Mysql Query with multiple subqueries with group by distinct condition - mysql

Hi I am a PHP Developer weak in MySQL Medium complex queries make my head fired.
The below is the table vulnerability.
+-----------------------------------------------------------------------------------------------+
| id | webisite_id | low_count| high_count | medium_count | date_time | vul_date |
+-----------------------------------------------------------------------------------------------+
| 20 | 6 | 1 | 1 | 1 | 2018-07-04 09:14:04 | 2018-02-01 |
| 19 | 6 | 30 | 30 | 30 | 2018-07-04 09:13:38 | 2018-01-30 |
| 18 | 6 | 1 | 1 | 1 | 2018-07-04 09:13:16 | 2018-01-01 |
+-----------------------------------------------------------------------------------------------+
This table represent count of low, medium, high - vulnerability count for each website in database. We can enter as many entries for each websites. But the only relevant entry for a website is the latest entry in each month (based on vul_date).
Here I need help I want query which fetch sum of counts low, high, medium of each websites of each month of last 1 year, for example if -> website with id 1 has 1, 2, 3 low, high, medium number of vulnerabilities, on month June and
-> that of with id 2 has 7, 8, 9 respectively the result should be for June 8, 10, 12. And like last 1 year's each month result should be get. If no entry it should be 0.
Note that the entries should be the maximum value of vul_date and if a site has multiple entries on the same vul_date get the latest date_time entry.
I tried to write question as much as simple. hope the question is understood.
Please help me on this
Thanks in advance.

I think below query will work for you.
SELECT
SUM(low_count),
SUM(medium_count),
SUM(high_count),
MONTH(vul_date)
FROM
(SELECT
low_count, medium_count, high_count, vul_date, date_time
FROM
test
WHERE
(website_id , vul_date) IN (SELECT website_id, MAX(vul_date)
FROM test GROUP BY website_id , MONTH(vul_date))) t
WHERE
date_time IN (SELECT MAX(date_time) FROM test GROUP BY website_id , vul_date)
GROUP BY MONTH(vul_date);
What it does is, first finds the latest entry month wise for each website id which is your max vul_date.
SELECT website_id, MAX(vul_date)
FROM test GROUP BY website_id , MONTH(vul_date)
If there are more than one entry for a vul_date, it uses date_time to select maximum value from them. Finally it sums all website date after grouping it month wise.
You can change the above query to get 0 value for those months where there is no entry for any websites.

DROP TABLE IF EXISTS T;
CREATE TABLE T(id INT, website_id INT, low_count INT, high_count INT, medium_count INT, date_time DATETIME, vul_date DATE);
INSERT INTO T VALUES
( 20 , 6 , 1 , 1 , 1, '2018-07-04 09:14:04' , '2018-02-01'),
( 19 , 6 , 30, 30, 30, '2018-07-04 09:13:38' , '2018-01-30'),
( 18 , 6 , 2 , 2 ,2 , '2018-07-04 09:13:16' , '2018-01-01'),
( 17 , 6 , 2 , 2 ,2 , '2018-07-04 09:12:01' , '2018-01-01'),
( 90 , 1,1,2,3,'2017-07-05 01:00:00',' 2017-07-06'),
( 90 , 2,8,9,10,'2017-07-05 01:00:00',' 2017-07-06');
select coalesce(c.yyyymm,d.yyyymm) yyyymm,
coalesce(c.lo,0) lo,
coalesce(c.hi,0) hi,
coalesce(c.med,0) med
from
(
SELECT concat(year(a.vul_date),'-',month(a.vul_date)) yyyymm,
SUM(LOW_COUNT) lo,SUM(HIGH_COUNT) hi,sum(medium_count) med
from
(
select website_id,date_time,vul_date
from t
where date_time = (select max(date_time) from t t1 where t1.website_id = t.website_id and t1.vul_date = t.vul_date)
) a
join
(select website_id, date_time,vul_date,
LOW_COUNT,HIGH_COUNT,medium_count
from t) b
on b.website_id = a.website_id and b.date_time = a.date_time
group by concat(year(a.vul_date),'-',month(a.vul_date))
) c
right join
(select distinct concat(year(dte),'-',month(dte)) yyyymm from dates d
where dte between date_sub(now(), interval 1 year) and now()
) d on d.yyyymm = c.yyyymm
;
Sub query a get the vul_date with the most recent data_time which is then self joined, aggregated and then infilled with missing dates using a right join to a dates/calender table. If you don't have a dates/calender it would be useful for this kind of exercise nut there are alternatives which you can find in SO.
Result
+---------+------+------+------+
| yyyymm | lo | hi | med |
+---------+------+------+------+
| 2017-7 | 9 | 11 | 13 |
| 2017-8 | 0 | 0 | 0 |
| 2017-9 | 0 | 0 | 0 |
| 2017-10 | 0 | 0 | 0 |
| 2017-11 | 0 | 0 | 0 |
| 2017-12 | 0 | 0 | 0 |
| 2018-1 | 32 | 32 | 32 |
| 2018-2 | 1 | 1 | 1 |
| 2018-3 | 0 | 0 | 0 |
| 2018-4 | 0 | 0 | 0 |
| 2018-5 | 0 | 0 | 0 |
| 2018-6 | 0 | 0 | 0 |
| 2018-7 | 0 | 0 | 0 |
+---------+------+------+------+
13 rows in set (0.04 sec)

Related

MySQL UNION ALL SELECT Issue on getting all the SUM per column

I wish to get the SUM of the product per column, not sure if the UNION ALL SELECT can handle the desired result considering that the first column is concatenated.
Here is the code:
SUM(product.product_id = 1) AS Soda,
SUM(product.product_id = 2) AS Liquor,
SUM(product.product_id = 3) AS Lemon,
SUM(product.product_id = 4) AS Mango,
SUM(product.product_id = 5) AS Inhaler,
SUM(1) AS Count
FROM line_item
JOIN product USING (product_id)
JOIN ( SELECT 0 lowest, 500 highest UNION
SELECT 501 , 1000 UNION
SELECT 1001 , 1500 UNION
SELECT 1501 , 2000 UNION
SELECT 2001 , 2500 ) ranges ON product.price * line_item.quantity BETWEEN ranges.lowest AND ranges.highest
GROUP BY ranges.lowest, ranges.highest
UNION ALL SELECT '','','','','','',
(
SELECT
COUNT(product.price * line_item.quantity)
FROM (line_item
INNER JOIN product ON line_item.product_id = product.product_id)
);
**The output:**
+-------------+------+--------+-------+-------+---------+-------+
| Revenue | Soda | Liquor | Lemon | Mango | Inhaler | Count |
+-------------+------+--------+-------+-------+---------+-------+
| 0 - 500 | 4 | 0 | 4 | 0 | 1 | 9 |
| 501 - 1000 | 0 | 0 | 0 | 2 | 0 | 2 |
| 1001 - 1500 | 0 | 1 | 0 | 2 | 2 | 5 |
| 1501 - 2000 | 0 | 2 | 0 | 0 | 1 | 3 |
| 2001 - 2500 | 0 | 1 | 0 | 0 | 0 | 1 |
| | | | | | | 20 |
+-------------+------+--------+-------+-------+---------+-------+
Thank for your help.
Have you tried ROLL UP operator? Seems like there's a similar problem to yours: Add a summary row with totals
I'm confused. You are defining the ranges already. If you want a range that encompasses all the values, just add it in:
FROM line_item JOIn
product
USING (product_id) JOIN
( SELECT 0 lowest, 500 highest UNION ALL
SELECT 501 , 1000 UNION ALL
SELECT 1001 , 1500 UNION ALL
SELECT 1501 , 2000 UNION ALL
SELECT 2001 , 2500 UNION ALL
SELECT 0 , 2500
-------^ all encompassing range
) ranges
ON product.price * line_item.quantity BETWEEN ranges.lowest AND ranges.highest
Voila! This also has the summary row.

Getting Average Count Between Datetime From/To Specific Hours

I would like to make a statistic for the record on my database where I want to calculate the average number when the user login to the system from/to certain datetime and in each 4 hours per day
simple example: I want to get the average of successful login from '2016-09-20 00:00:00' to '2016-09-23 23:59:59' where the result should be given on these certain times ('00:00:00' - '11:59:59') and ('12:00:00' - '23:59:59')
This is a list of an example data (where status 1 means success, 0 meant not):
| id | | driver_id | login_timedate | status |
| 1 | | 1 | '2016-09-20 00:00:11' | 1 |
| 2 | | 2 | '2016-09-20 01:16:09' | 1 |
| 3 | | 2 | '2016-09-20 23:01:16' | 1 |
| 4 | | 3 | '2016-09-21 04:04:59' | 1 |
| 5 | | 3 | '2016-09-21 05:06:59' | 0 |
| 6 | | 2 | '2016-09-21 16:06:59' | 1 |
| 7 | | 1 | '2016-09-22 00:16:59' | 1 |
| 8 | | 2 | '2016-09-23 04:09:22' | 0 |
| 9 | | 1 | '2016-09-23 06:22:59' | 1 |
| 10 | | 3 | '2016-09-23 22:09:22' | 1 |
| 11 | | 1 | '2016-09-24 00:00:22' | 1 |
So in this case I'll get total number of success login from (20-23 / 09 / 2016) are: 8 (day1= 3 , day2= 2 , day3= 1 , day4= 2)
Total number of success each day within the range from ('00:00:00' - '11:59:59') are 5 (day1= 2 , day2= 1 , day3= 1 , day4= 1)
Average: 5 / 4 = 1.25
Total number of success each day within the range from ('00:00:00' - '11:59:59') are 3 (day1= 1 , day2= 0 , day3= 1 , day4= 1)
Average: 3 / 4 = 0.75
I have did the first part to get the total number of success login within datetime range this is my code (which will return 8)
SET #start_date = '2016-09-20';
SET #start_taime = '00:00:00';
SET #end_date = '2016-09-23';
SET #end_time = '23:59:59';
SELECT SUM(`total_logins`.`number_of_success`) FROM (
SELECT COUNT( `login_logs`.`driver_id` ) AS `number_of_success`
FROM `login_logs`
WHERE `login_logs`.`status` = 1
AND
`login_logs`.`login_timedate` >= CONCAT(#start_date, ' ', #start_time)
AND
`login_logs`.`login_timedate` <= CONCAT(#end_date, ' ', #end_time)
GROUP BY `login_logs`.`user_id`
) AS `total_logins`
#Update:
Expected Output for this code:
| total_logins |
| 8 |
I would like to do the next part which calculate the average logins within the same datetime range from XX:XX:XX time to YY:YY:YY time such as this:
Total number of success each day within the range from ('00:00:00' - '11:59:59') are 5 (day1= 2 , day2= 1
, day3= 1 , day4= 1)
Average: 5 / 4 = 1.25
#Update:
Expected Output After modifying my code to get avrage from ('00:00:00' - '11:59:59') :
| Avrage_00_12 |
| 1.25 |
How should I modify the code to implement this part?
I hope that you understood my question
thank you for your help in advanced
You can use the following query:
SELECT SUM(`number_of_success`) AS `total_success`,
SUM(`success_range1`) / COUNT(*) AS `average1`,
SUM(`success_range2`) / COUNT(*) AS `average2`
FROM (
SELECT DATE(`login_logs`.`login_timedate`),
COUNT( `login_logs`.`driver_id` ) AS `number_of_success`,
COUNT(CASE
WHEN TIME(`login_logs`.`login_timedate`)
BETWEEN '00:00:00' AND '11:59:59'
THEN 1
END) AS `success_range1`,
COUNT(CASE WHEN TIME(`login_logs`.`login_timedate`)
BETWEEN '12:00:00' AND '23:59:59'
THEN 1
END) AS `success_range2`
FROM `login_logs`
WHERE `login_logs`.`status` = 1
AND
`login_logs`.`login_timedate` >= '2016-09-20 00:00:00'
AND
`login_logs`.`login_timedate` <= '2016-09-23 23:59:59'
GROUP BY DATE(`login_logs`.`login_timedate`)) AS t
Output:
total_success, average1, average2
----------------------------------
8, 1.2500, 0.7500

How I can create this SELECT query in MYSQL?

I am having two MYSQL tables. These are named as :
attendance_summary, attendance_summary_cecb. Both tables have same fields but may have different data.
These are the result the month of August, 2016 from this two tables.
mysql> SELECT emp_id, date, days, ot_days, ot_hours FROM attendance_summary;
+--------+------------+------+---------+----------+
| emp_id | date | days | ot_days | ot_hours |
+--------+------------+------+---------+----------+
| 4 | 2016-08-01 | 10 | 0 | 20.0 |
| 4 | 2016-08-16 | 13 | 0 | 14.5 |
| 12 | 2016-08-01 | 12 | 0 | 10.0 |
| 12 | 2016-08-16 | 11 | 2 | 16.5 |
| 14 | 2016-08-01 | 12 | 2 | 15.0 |
| 14 | 2016-08-16 | 10 | 0 | 16.0 |
| 15 | 2016-08-01 | 11 | 0 | 16.0 |
| 15 | 2016-08-16 | 9 | 3 | 21.0 |
+--------+------------+------+---------+----------+
8 rows in set (0.00 sec)
mysql> SELECT emp_id, date, days, ot_days, ot_hours FROM attendance_summary_cecb;
+--------+------------+------+---------+----------+
| emp_id | date | days | ot_days | ot_hours |
+--------+------------+------+---------+----------+
| 4 | 2016-08-01 | 15 | 0 | 20.0 |
| 4 | 2016-08-16 | 10 | 0 | 12.0 |
| 12 | 2016-08-01 | 12 | 1 | 10.0 |
| 12 | 2016-08-16 | 11 | 2 | 16.5 |
| 14 | 2016-08-01 | 12 | 2 | 15.0 |
| 14 | 2016-08-16 | 10 | 0 | 16.0 |
| 15 | 2016-08-01 | 10 | 0 | 16.0 |
| 15 | 2016-08-16 | 9 | 0 | 21.0 |
+--------+------------+------+---------+----------+
8 rows in set (0.00 sec)
Using this two tables, In a single query, I need to get
> From `attendance_summary`
> - Total Days
> - Total OT Days
> - Total OT Hours
>
> From `attendance_summary_cecb`
> - Total Days
> - Total OT Days
> - Total OT Hours
This is how I tried it:
SELECT e.emp_id
, e.full_name
, MAX(days1) as days1
, MAX(ot_days1) as ot_days1
, MAX(ot_hours1) as ot_hours1
, MAX(days2) as days2
, MAX(ot_days2) as ot_days2
, MAX(ot_hours2) as ot_hours2
, SUM(days1+days2) AS olekma_days
, SUM(ot_days1+ot_days2) AS olekma_ot_days
, SUM(ot_hours1+ot_hours2) AS olekma_ot_hours
, SUM(days3+days4) AS cecb_days
, SUM(ot_days3+ot_days4) AS cecb_ot_days
, SUM(ot_hours3+ot_hours4) AS cecb_ot_hours
FROM
(
SELECT emp_id
, CASE DAY(date) WHEN 1 THEN days ELSE 0 END as days1
, CASE DAY(date) WHEN 1 THEN ot_days ELSE 0 END as ot_days1
, CASE DAY(date) WHEN 1 THEN ot_hours ELSE 0 END as ot_hours1
, CASE DAY(date) WHEN 16 THEN days ELSE 0 END as days2
, CASE DAY(date) WHEN 16 THEN ot_days ELSE 0 END as ot_days2
, CASE DAY(date) WHEN 16 THEN ot_hours ELSE 0 END as ot_hours2
FROM attendance_summary
WHERE MONTH(date) = 08 AND YEAR(date) = 2016
UNION
SELECT emp_id
, CASE DAY(date) WHEN 1 THEN days ELSE 0 END as days3
, CASE DAY(date) WHEN 1 THEN ot_days ELSE 0 END as ot_days3
, CASE DAY(date) WHEN 1 THEN ot_hours ELSE 0 END as ot_hours3
, CASE DAY(date) WHEN 16 THEN days ELSE 0 END as days4
, CASE DAY(date) WHEN 16 THEN ot_days ELSE 0 END as ot_days4
, CASE DAY(date) WHEN 16 THEN ot_hours ELSE 0 END as ot_hours4
FROM attendance_summary_cecb
WHERE MONTH(date) = 08 AND YEAR(date) = 2016
) as s
LEFT JOIN employees e USING (emp_id)
INNER JOIN categories c ON c.id = e.category_id AND c.salary_type = 'D'
WHERE e.category_id = 6
GROUP BY e.emp_id\G
UPDATE:
This is the table structure for these two tables:
CREATE TABLE `attendance_summary` (
`as_id` INT(11) NOT NULL AUTO_INCREMENT,
`emp_id` INT(11) NOT NULL,
`date` DATE NOT NULL DEFAULT '1901-01-01',
`days` INT(11) NOT NULL,
`ot_days` INT(11) NULL,
`ot_hours` DECIMAL(16,1) NULL,
PRIMARY KEY (`as_id`),
UNIQUE KEY (emp_id, `date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `attendance_summary_cecb` (
`as_id` INT(11) NOT NULL AUTO_INCREMENT,
`emp_id` INT(11) NOT NULL,
`date` DATE NOT NULL DEFAULT '1901-01-01',
`days` INT(11) NOT NULL,
`ot_days` INT(11) NULL,
`ot_hours` DECIMAL(16,1) NULL,
PRIMARY KEY (`as_id`),
UNIQUE KEY (emp_id, `date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
But it doesn't work for me. Hope somebody may help me out.
The answer to your question is rather simple. Btw I had to create 2 missing tables from the bottom to confirm your error.
When you do a
select col1, col2 from t1
union
select col9 as fred, col11 as john from t2
or any variation on that theme with scads of tables, the only column names that come back are from the first member of the union.
In other words, fred and john don't come out.
So your days3 and days4 are not available coming out of the derived table s.
You need to rethink it then, but that answers the question :p
From the MySQL Manual Page entitled UNION Syntax:
The column names from the first SELECT statement are used as the
column names for the results returned. Selected columns listed in
corresponding positions of each SELECT statement should have the same
data type. (For example, the first column selected by the first
statement should have the same type as the first column selected by
the other statements.)

query the database to find what Nth is the record

I don't know how to writ the Title for this question, but what I need is a query that return what is the N record with a specific value.
The table that I have is over 5.2M records
The records are similar to:
session (string, primary indexed)
customer_id (int, indexed)
clicks (int, indexed)
order_number (int, indexed)
date_entry (datetime, indexed)
many other fields
what I need to know is how many times the same customer logged into the site (different sessions) before placing an order (order_number is 0 unless an order is placed during that session)
a sample data can be (simplify data)
session | c_id | clicks | ord_num | entry |
abc | 123 | 2 | 0 | 2012-08-01 00:00:00 |
cde | 456 | 2 | 0 | 2012-08-01 00:00:01 |
efg | 457 | 2 | 0 | 2012-08-01 00:00:02 |
hij | 123 | 5 | 0 | 2012-08-01 00:00:03 |
kod | 986 | 10 | 0 | 2012-08-01 00:00:04 |
wdg | 123 | 2 | 9876 | 2012-08-01 00:00:05 |
qwe | 123 | 2 | 0 | 2012-08-01 00:00:06 |
wvr | 986 | 12 | 8656 | 2012-08-01 00:00:07 |
What I want is a query that shows something similar to:
entry - date entry
tot_sess - total number of session
tot_cust - total number of customers
1sess - customer1 with only one session
2sess - customers with 2 sessions
3sess - customers with 3 sessions
4sess - customers with 4 sessions
more4sess - customers with more than 4 sessions
order1sess - customers that ordered on the first session
order2sess - customers that ordered on the second session
order3sess - customers that ordered on the third session
order4sess - customers that ordered on the fourth session
orderMore4Sess - customers that ordered after the fourth session
entry |tot_sess|tot_cust| 1sess | 2sess | 3sess | 4sess | more4sess | order1sess | order2sess | order3sess | order4sess | orderMore4Sess |
2012-08-01 | 8 | 4 | 2 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
I am already able to get the information about the session with the following query:
SELECT
t.`date_entry`,
COUNT(sess) `cust`,
SUM(sess) `session`,
COUNT(IF(sess>1,sess,NULL)) `more than once`,
COUNT(IF(sess=1,sess,NULL)) `one`,
COUNT(IF(sess=2,sess,NULL)) `two`,
COUNT(IF(sess=3,sess,NULL)) `three`,
COUNT(IF(sess=4,sess,NULL)) `four`,
COUNT(IF(sess>4,sess,NULL)) `more`,
ROUND(COUNT(IF(sess>1,sess,NULL))/COUNT(sess),2) `perc > 1`,
ROUND(COUNT(IF(sess>2,sess,NULL))/COUNT(sess),2) `perc > 2`,
ROUND(COUNT(IF(sess>3,sess,NULL))/COUNT(sess),2) `perc > 3`,
ROUND(COUNT(IF(sess>4,sess,NULL))/COUNT(sess),2) `perc > 4`
FROM
(
SELECT
`customer_id`,
COUNT(`session`) `sess`,
DATE(`date_entry`) `date_entry`
FROM `customer_activity_log`
WHERE
`clicks` > 1
AND `customer_id` > 0
AND `date_entry` > '2012-08-01'
AND subsite_id <=1
GROUP BY `date_entry`, `customer_id`
) t
GROUP BY date_entry
Once I had that I will also need to look at the data in a different way, for example, if customer 123 showed on the first time on 2012-01-01 and then came back 15 times and placed the order on 2012-08-01 and then came back 5 more times and placed another order on 2012-10-12 I will need a query that will not restrain by date but only by customer, in other words the restrain date_entry will be removed
I hope it makes sense
SELECT e AS entry,
SUM(sessions) AS tot_sess,
COUNT(*) AS tot_cust,
SUM(sessions=1) AS 1sess,
SUM(sessions=2) AS 2sess,
SUM(sessions=3) AS 3sess,
SUM(sessions=4) AS 4sess,
SUM(sessions>4) AS more4sess,
SUM(orders =1) AS order1sess,
SUM(orders =2) AS order2sess,
SUM(orders =3) AS order3sess,
SUM(orders =4) AS order4sess,
SUM(orders >4) AS orderMore4Sess
FROM (
SELECT b.e, b.c_id, b.sessions, COUNT(a.entry) AS orders
FROM customer_activity_log a RIGHT JOIN (
SELECT DATE(entry) AS e, c_id, COUNT(*) AS sessions,
MIN(IF(ord_num=0,NULL,entry)) AS o
FROM customer_activity_log
GROUP BY e, c_id
) b ON a.c_id = b.c_id AND DATE(a.entry) = b.e AND a.entry <= b.o
GROUP BY b.e, b.c_id
) t
See it on sqlfiddle.

MySQL Query output using if else or case..?

I have a table as
mysql> select * FROM testa;
+---------+-------+
| month_x | money |
+---------+-------+
| 11101 | 12345 |
| 11105 | 100 |
| 11105 | 100 |
| 11105 | 100 |
| 11105 | 100 |
| 11106 | 12345 |
+---------+-------+
6 rows in set (0.00 sec)
where last two digits in the month_x are months now i want my output as
Month TOTAL
01 12345
02 0
03 0
04 0
05 400
06 12345
07 0
08 0
09 0
10 0
11 0
12 0
IS possible using the If else or case.
You can use modular arithmetic to obtain the trailing two digits (they're the remainder when the number is divided by 100), then assuming you wish to sum money when your data is "grouped by" month:
SELECT month_x % 100 AS Month, SUM(money) AS TOTAL
FROM testa
GROUP BY Month
ORDER BY Month ASC;
Alternatively, you could use rely on MySQL's implicit type conversion and use its string functions:
SELECT RIGHT(month_x, 2) AS Month, SUM(money) AS TOTAL
FROM testa
GROUP BY Month
ORDER BY Month ASC;
UPDATE
As #shiplu.mokadd.im states, to show every month (even those for which you have no data), you need to obtain numbers 1 through 12 from a temporary table. However, you can create such a temporary table in your query using UNION:
SELECT 1
UNION SELECT 2
UNION SELECT 3 -- etc
Therefore:
SELECT Month, Sum(money) AS TOTAL
FROM testa
RIGHT JOIN (
SELECT 1 AS 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
) months ON testa.month_x % 100 = months.Month
GROUP BY Month;
HOWEVER I would note that usually one doesn't usually do this in the database, as it really belongs in the presentation layer: from whatever language you're accessing the database, you'd loop over 1...12 and assume TOTAL to be 0 if there's no corresponding record in the resultset.
For this you need to create a table first with months' numeric value in it.
CREATE TABLE `months` (
`mon` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `months` VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12);
Then execute this query,
SELECT m.mon,
IF(Sum(t.money) IS NULL, 0, Sum(t.money)) AS `money`
FROM testa t
RIGHT OUTER JOIN months m
ON ( t.month_x%100 = m.mon )
GROUP BY m.mon;
Result is,
+------+-------+
| mon | money |
+------+-------+
| 1 | 12345 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 400 |
| 6 | 12345 |
| 7 | 0 |
| 8 | 0 |
| 9 | 0 |
| 10 | 0 |
| 11 | 0 |
| 12 | 0 |
+------+-------+
You can use IF statements - yes.
Look # this:
http://dev.mysql.com/doc/refman/5.5/en/if-statement.html