Mysql - filling rows for missing months - mysql

Gosh, it must be so simple but I'm struggling with this 'filling out missing data' issue.
I have a table that has the following columns with some inserted data.
TABLE
year month payment
2014 3 100
2014 5 800
2014 9 200
And what I want from this table is to have a full range of months with its payment value from 2014.
Month Payment
1 0
2 0
3 100
4 0
5 800
...
12 0
I tried using IFNULL in select but failed so bad... and search results from stackoverflow usually join two or more tables to manipulate information. What would be the fastest and best solution to solve this problem?

For missing months you can have a union query with all months and join with your table
SELECT
t1.`year`,
t.`month`,
coalesce(t1.payment,0) payment
FROM
(SELECT 1 AS `month`
UNION
SELECT 2 AS `month`
UNION
....
SELECT 12 AS `month`
) AS t
LEFT JOIN your_table t1 on(t.`month` = t1.`month`)
WHERE ....
Fiddle Demo

Related

mysql coalesce for null values in joined table

The stucture of table day and report is as below.
day
id day
1 sunday
2 monday
3 tuesday
4 thursday
5 friday
6 saturday
report
id dta day ndx
1 10 1 1
2 15 2 1
3 14 3 1
4 15 4 1
5 12 5 1
6 11 6 1
7 55 1 2
8 23 2 2
9 10 3 2
10 19 4 2
Need to be group by report.ndx, so data would be managed as index wise
ndx-1, sunday-10, monday-15, tuesday-14, thursday-15, friday-12, saturday-11
ndx-2, sunday-55, monday-23, tuesday-10, thursday-19
Sql Syntax with group by
SELECT report.ndx AS ndx, GROUP_CONCAT(day.day,'-',report.dta) AS data
FROM report
LEFT JOIN day ON day.id = report.day
GROUP BY report.ndx
But requirment is placing null value if day is not in report table. For example
ndx-2, sunday-55, monday-23, tuesday-10, thursday-19, friday-0, saturday-0
So what I tried with coalesce
SELECT report.ndx AS ndx, GROUP_CONCAT(day.day,'-',COALESCE(report.dta),'0') AS data
FROM report
LEFT JOIN day ON day.id = report.day
GROUP BY report.ndx
But I think, as report is primary in SQL JOIN Statement, so coalesce is useless to use setting null value.
I tried with case when
SELECT GROUP_CONCAT(DISTINCT CASE
WHEN day.id=report.day
THEN CONCAT(day.day,'-',report.dta)
ELSE CONCAT(day.day,'-',0) END) AS data
FROM report
LEFT JOIN day ON 1=1
GROUP BY report.ndx
But it prints once null value with day and again value with matched day in same index. For example
sunday-10, sunday-0
You have 2 mistakes:
COALESCE requires at least 2 arguments to be meaningful but you have provided only 1 argument report.dta
you are joining the tables in the wrong order. If you want to get all days from table day even if no record in table report references that day - then you should put day in the FROM clause and then join report on the left
You can try something like this:
SELECT ndx, GROUP_CONCAT(CONCAT(day,'-',dta))
FROM
(SELECT d.id, d.day, IFNULL(dta,0) dta, IFNULL(ndx,1) ndx
FROM day d LEFT JOIN report r
ON d.id=r.day AND ndx=1
UNION ALL
SELECT d.id, d.day, IFNULL(dta,0) dta, IFNULL(ndx,2) ndx
FROM day d LEFT JOIN report r
ON d.id=r.day AND ndx=2) A
GROUP BY ndx
Make a LEFT JOIN query between day and report tables.
Separate the queries by ndx value and UNION ALL it.
Replace NULL values using IFNULL(dta,0) for dta and IFNULL(ndx,[value]); where the [value] is whatever value of ndx in WHERE.
Turn that query to become sub-query.
Type in the SELECT ndx, GROUP_CONCAT(... condition then GROUP BY ndx.
Demo fiddle

Get last 7 days count even when there are no records

How can I make a SQL query that returns me something like
---------------------
|DATE | Count |
---------------------
|2015/01/07 | 7 |
|2015/01/06 | 0 |
|2015/01/05 | 8 |
|2015/01/04 | 5 |
|2015/01/03 | 0 |
|2015/01/02 | 4 |
|2015/01/01 | 2 |
---------------------
When there are no records for the 6th and 3rd?
You need a table of all the sequence numbers from 0 to 6. This is easy to generate in a simple query, as follows.
SELECT 0 AS seq
UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6
Next, let's use this to construct a virtual table of seven dates. For this example, we pick today and the six preceding days.
SELECT DATE(NOW())-INTERVAL seq.seq DAY theday
FROM (
SELECT 0 AS seq
UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6
) seq
Then you do your summary query. You didn't say exactly how it goes so I will guess. This one gives you the records from six days ago until today. Today is still in progress.
SELECT DATE(i.item_time) theday
COUNT(*) `count`
FROM items i
WHERE i.item_time >= DATE(NOW()) - INTERVAL 6 DAYS
GROUP BY DATE(i.item_time)
Finally, starting with the list of days, let's LEFT JOIN that summary to it.
SELECT thedays.theday, IFNULL(summary.`count`,0) `count`
FROM (
SELECT DATE(NOW())-INTERVAL seq.seq DAY theday
FROM (
SELECT 0 AS seq
UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6
) seq
) thedays
LEFT JOIN (
SELECT DATE(i.item_time) theday
COUNT(*) `count`
FROM items i
WHERE i.item_time >= DATE(NOW()) - INTERVAL 6 DAYS
GROUP BY DATE(i.item_time)
) summary USING (theday)
ORDER BY thedays.theday
It looks complex, but it is simply the combination of three basic queries. Think of it as a sandwich, with bread and cheese and tomato stuck together with an ORDER BY toothpick.
Here's a more thorough writeup. http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/
MariaDB version 10 has built-in virtual tables of cardinal number sequences like seq_0_to_6. This is convenient.
You need to build a dummy dates table and left join your current table against it.
SELECT dummy.date, SUM(IFNULL(yourtable.record,0)) recordcount
FROM dummy
LEFT JOIN yourtable on dummy.date=yourtable.date
GROUP BT dummy.date
please note that I'm replacing nulls with a zero.
One solution is to create a calendar table containing all the dates you need. You can then left join it to your data to get what you are after
First of all you have to use left join, converting NULLs to 0s using the IFNULL function. Try to match your table and use left join.

MySQL - How to sum up first occurrences from many different products

I have a big view called: how_many_per_month
name_of_product | how_many_bought | year | month
p1 20 2012 1
p2 7 2012 1
p1 10 2012 2
p2 5 2012 2
p1 3 2012 3
p2 20 2012 3
p3 66 2012 3
How to write MySQL query in order to get only first few occurences of product p1, p2, p3 at once?
To get it one by one for first 3 months I can write:
SELECT name_of_product , sum(how_many_bought) FROM
(SELECT name_of_product, how_many_bought FROM `how_many_per_month`
WHERE name_of_product= 'p1' LIMIT 3) t
How to do it to all possible products at once so my result for taking only first month is like:
p1 20
p2 7
p3 66
For two months:
p1 30
p2 12
p3 66
The problem is that some products are published in different months and I have to make statistic how many of total of them are sold in first month, first 3 months, 6 months, 1 year divided by total.
Example using union
select
name_of_product,
sum(how_many_bought) as bought,
"first month" as period
from how_many_per_month
where month = 1
group by name_of_product
union
select
name_of_product,
sum(how_many_bought) as bought,
"first 2 month" as period
from how_many_per_month
where month <= 2
group by name_of_product
union
select
name_of_product,
sum(how_many_bought) as bought,
"first 6 month" as period
from how_many_per_month
where month <= 6
group by name_of_product
union
select
name_of_product,
sum(how_many_bought) as bought,
"first 12 month" as period
from how_many_per_month
where month <= 12
group by name_of_product
Demo: http://www.sqlfiddle.com/#!2/788ea/11
Results are different a little bit from your expectation. Are you sure that you write them properly? If you need to gain more speed in query time you can use group by case as I've already said.
I'm not quite sure what you're trying to achieve as the description of your question is a bit unclear. From what I've read so far, I understand you want to show the total of how many ITEM_X, ITEM_Y, ITEM_Z were sold for the past 1,3,6 months.
Based on the data you've provided, I've created this sqlfiddle that sums all results and groups them by item. This is the query:
SELECT
name_of_product,
sum(how_many_bought) as how_many_bought
FROM how_many_per_month
WHERE year = 2012
AND month BETWEEN 1 AND 3
GROUP BY name_of_product
-- NOTE: Not specifying an year will result in including all "months"
which are between the values 1 and 3 for all years. Remove it
in case you need that effect.
In the example above the database will sum all sold items between months 1 and 3 (including) for 2012. When you execute this query in your application just change the range in the BETWEEN X AND X and you'll be good to go.
Additional tip:
Avoid using sub-queries or try using the as a last resort method (in case there's simply no other way to do it). They are significantly slower than normal and even join queries. Usually sub-queries can be transformed into a join query.
SELECT
hmpm.name_of_product , SUM(hmpm.how_many_bought)
FROM (
SELECT name_of_product
FROM how_many_per_month
/* WHERE ... */
/* ORDER BY ... */
) sub
INNER JOIN how_many_per_month hmpm
ON hmpm.name_of_product = sub.name_of_product
GROUP BY hmpm.name_of_product
/* LIMIT ... */
MySQL not support LIMIT in subquery, but you need ordering and condition. And why not have id_of_product field?

MySQL Group by week num w/ multiple date column

I have a table with columns similar to below , but with about 30 date columns and 500+ records
id | forcast_date | actual_date
1 10/01/2013 12/01/2013
2 03/01/2013 06/01/2013
3 05/01/2013 05/01/2013
4 10/01/2013 09/01/2013
and what I need to do is get a query with output similar to
week_no | count_forcast | count_actual
1 4 6
2 5 7
3 2 1
etc
My query is
SELECT weekofyear(forcast_date) as week_num,
COUNT(forcast_date) AS count_forcast ,
COUNT(actual_date) AS count_actual
FROM
table
GROUP BY
week_num
but what I am getting is the forcast_date counts repeated in each column, i.e.
week_no | count_forcast | count_actual
1 4 4
2 5 5
3 2 2
Can any one please tell me the best way to formulate the query to get what I need??
Thanks
try:
SELECT weekofyear(forcast_date) AS week_forcast,
COUNT(forcast_date) AS count_forcast, t2.count_actual
FROM
t t1 LEFT JOIN (
SELECT weekofyear(actual_date) AS week_actual,
COUNT(forcast_date) AS count_actual
FROM t
GROUP BY weekOfYear(actual_date)
) AS t2 ON weekofyear(forcast_date)=week_actual
GROUP BY
weekofyear(forcast_date), t2.count_actual
sqlFiddle
You have to write about 30 (your date columns) left join, and the requirement is that your first date column shouldn'd have empty week (with a count of 0) or the joins will miss.
Try:
SELECT WeekInYear, ForecastCount, ActualCount
FROM ( SELECT A.WeekInYear, A.ForecastCount, B.ActualCount FROM (
SELECT weekofyear(forecast_date) as WeekInYear,
COUNT(forecast_date) as ForecastCount, 0 as ActualCount
FROM TableWeeks
GROUP BY weekofyear(forecast_date)
) A
INNER JOIN
( SELECT * FROM
(
SELECT weekofyear(forecast_date) as WeekInYear,
0 as ForecastCount, COUNT(actual_date) as ActualCount
FROM TableWeeks
GROUP BY weekofyear(actual_date)
) ActualTable ) B
ON A.WeekInYear = B.WeekInYear)
AllTable
GROUP BY WeekInYear;
Here's my Fiddle Demo
Just in case someone else comes along with the same question:
Instead of trying to use some amazing query, I ended up creating an array of date_columns_names and a loop in the program that was calling this query, and for each date_column_name, performing teh asme query. It is a bit slower, but it does work

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