How to count in a range of result in mysql - mysql

l have a record table now, and l must to statistics the result of every month.
here is a test table
+----+------+----------+----------+------+
| id | name | grade1 | grade2 | time |
+----+------+----------+----------+------+
| 1 | a | 1 | 1 | 1 |
| 2 | a | 0 | 1 | 1 |
| 3 | a | 1 | 2 | 2 |
| 4 | b | 1 | 2 | 2 |
| 5 | a | 1 | 1 | 2 |
+----+------+----------+----------+------+
5 rows in set (0.01 sec)
time column means month(the actual is timestamp).
l need to statistics total number those grade1 >=1 && grade2 >=1 in every month
So, l want to get the result like this
+----+------+----------+----------+----------+----------+------+
| id | name | grade1_m1| grade2_m1| grade1_m2| grade2_m2| time |
+----+------+----------+----------+----------+----------+------+
| 13 | a | 1 | 2 | null | null | 1 |
| 14 | a | null | null | 2 | 2 | 2 |
| 15 | b | null | null | 1 | 1 | 2 |
+----+------+----------+----------+----------+----------+------+
3 rows in set (0.00 sec)
fake code of sql seem like this:
select
count(grade1 where time=1 and grade1 >= 1) as grade1_m1,
count(grade2 where time=1 and grade2 >= 1) as grade1_m1,
count(grade1 where time=2 and grade1 >= 1) as grade1_m2,
count(grade2 where time=2 and grade2 >= 1) as grade1_m2,
-- ... 12 months' statistics
from test
group by name
In the fact, l done it, but with temporary table like follow:
select
count(if(m1.grade1>=1, 1, null)) as grade1_m1,
count(if(m1.grade2>=1, 1, null)) as grade2_m1,
count(if(m2.grade1>=1, 1, null)) as grade1_m2,
count(if(m2.grade2>=1, 1, null)) as grade2_m2,
-- ...
from test
left join
(select * from test where time = 1) as m1
on m1.id = test.id
left join
(select * from test where time = 1) as m2
on m2.id = test.id
-- ...
group by name
But this sql is toooooooo long. this test table is just a simple version. Under real situation, l printed my sql and that took up two screens in chrome. So l am seeking a more simple way to complete it

You're original version is almost there. You need case and sum() is more appropriate:
select name,
sum(case when time=1 and grade1 >= 1 then grade1 end) as grade1_m1,
sum(case when time=1 and grade2 >= 1 then grade2 end) as grade2_m1,
sum(case when time=2 and grade1 >= 1 then grade1 end) as grade1_m2,
sum(case time=2 and grade2 >= 1 then grade2 end) as grade2_m2,
-- ... 12 months' statistics
from test
group by name

Related

Mysql - Create view with case and count

I'm trying to create a view with CASE and COUNT from this two tables
t_jobs
| job_id |
+--------+
| 1 |
| 2 |
| 3 |
| 4 |
t_emails
| email_id | job_id | email_log |
+----------+--------+-----------+
| 1 | 1 | OK |
| 2 | 1 | ERROR |
| 3 | 2 | ERROR |
| 4 | 3 | OK |
The result should be this
view_jobs
| job_id | email_check | email_total |
+--------+-------------+-------------+
| 1 | 1 | 2 |
| 2 | 1 | 1 |
| 3 | 0 | 1 |
| 4 | 0 | 0 |
I'm trying in this way but with no success
SELECT
j.job_id,
CASE WHEN MAX( e.email_log != 'OK' ) THEN 1 ELSE 0 END email_check
COUNT( e.email_log ) AS email_total
FROM t_jobs j
LEFT OUTER JOIN `t_emails` `e` ON `j`.`job_id` = `e`.`job_id`
GROUP BY
j.job_id;
Can you tell me how can I do?
Thanks
I guess you need a simple conditional aggregation -
SELECT j.job_id,
COUNT(CASE WHEN e.email_log <> 'OK' THEN 1 ELSE NULL END) email_check,
COUNT(e.email_log) AS email_total
FROM t_jobs j
LEFT OUTER JOIN `t_emails` `e` ON `j`.`job_id` = `e`.`job_id`
GROUP BY j.job_id;
You just forgot a comma(,) at end of second column.
I also modified your condition a little bit.
Here email_check 1 denotes that job_id has error
0 denotes it has no error.
SELECT
j.job_id,
CASE WHEN MIN( e.email_log ) = 'error' THEN 1 ELSE 0 END as email_check,
COUNT( e.email_log ) AS email_total
FROM t_jobs j
LEFT OUTER JOIN `t_emails` `e` ON `j`.`job_id` = `e`.`job_id`
GROUP BY
j.job_id;

Filter and sort by a calculated column using case (column not found)

I am trying to filter and sort by a calculated column using case but I'm getting a syntax error.
Column difference_sum and column payments_sum not found
I need to be able to have a WHERE and ORDER BY clause.
I have two tables 'contracts' and 'payments'.
Contracts table
+----+-----------------+-----------+-----------+
| id | contract_number | legal_sum | bonus_sum |
+----+-----------------+-----------+-----------+
| 1 | 110258465651 | 50 | 20 |
| 2 | 564984656355 | 15 | 12 |
| 3 | 548498415165 | 150 | 35 |
+----+-----------------+-----------+-----------+
Payments table
+----+--------------+----------+
| id | contract_id | paid_sum |
+----+--------------+----------+
| 1 | 564984656355 | 7 |
| 2 | 564984656355 | 1 |
| 3 | 564984656355 | 2 |
+----+--------------+----------+
First I need to calculate the difference between legal sum and bonus sum (difference_sum)
Then I need to get the sum of payments.paid_sum (payments_sum)
Then I need to sort by the criteria if (payments_sum - difference_sum <= 0)
So I made this query but it says that difference_sum does not exist:
SELECT
(contracts.legal_sum - contracts.bonus_sum) as difference_sum
sum(payments.paid_sum) as payments_sum,
CASE
WHEN (difference_sum - payments_sum) <= 0 THEN "All paid"
WHEN (difference_sum - payments_sum) > 0 THEN "Not paid"
END AS isPaid
FROM contracts
INNER JOIN payments on contracts.id = payments.contract_id
WHERE isPaid = "All paid"
ORDER BY isPaid
Example output:
+----------------+--------------+----------+
| difference_sum | payments_sum | isPaid |
+----------------+--------------+----------+
| 30 | 30 | All paid |
| 48 | 15 | Not paid |
| 100 | 100 | All paid |
+----------------+--------------+----------+
Try
SELECT difference_sum, payments_sum, isPaid
FROM (
SELECT payments_sum,
(contracts.legal_sum - contracts.bonus_sum) as difference_sum
CASE
WHEN (contracts.legal_sum - contracts.bonus_sum - payments_sum) <= 0 THEN "All paid"
WHEN (contracts.legal_sum - contracts.bonus_sum - payments_sum) > 0 THEN "Not paid"
END AS isPaid
FROM contracts
INNER JOIN (
SELECT contract_id, sum(payments.paid_sum) as payments_sum
FROM payments
GROUP BY contract_id
) payments on contracts.id = payments.contract_id
) q
WHERE isPaid = "All paid"
-- why order by a single value ?
-- ORDER BY isPaid
Probably you also need to add contract_id column to make it more useful.

MYSQL condition inside case not working

I have two tables: Here is the
sqlfiddle (http://sqlfiddle.com/#!9/5a51734/5)
1) [table:data_aoc]
| aoc_id | aoc_name | aoc_type | client_id |
|------------------------------|-----------|
1 | MA01 | sensor_1 | 4 | 1 |
2 | MA02 | sensor_2 | 15 | 1 |
2) table:data_log
| log_id | log_aoc_id | trans_type | trans_value | trans_date |
|-------------------------------------------------------------|
1 | x1a1 | MA01 | 0 | 90 | 2017-10-20 |
2 | afaf | MA01 | 0 | 90 | 2017-10-21 |
3 | va12 | MA02 | 0 | 10 | 2017-10-21 |
4 | gag2 | MA02 | 0 | 10 | 2017-11-25 |
Expected Result
Total value for MA02 should be 10 but it shows 20
my queries as follows
SELECT
(CASE
WHEN a.aoc_type IN ('4')
THEN IFNULL((SUM(b.trans_value * case b.trans_type when '0' then -1 else 1 end)),0)
WHEN a.aoc_type IN ('15')
THEN IFNULL((SUM(b.trans_value * case when b.trans_type='0' AND DATE(b.trans_date) <= DATE(NOW()) then -1 else 1 end)),0)
END) as total
FROM data_aoc a
LEFT JOIN data_log b ON b.log_aoc_id = a.aoc_id
WHERE a.client_id='1'
GROUP BY a.aoc_name
ORDER BY a.aoc_id asc
Iam expecting when aoc_type is (15) it will sum the value within the date condition
DATE(b.trans_date) <= DATE(NOW())
but when i execute the queries, it produce result not within the date condition. *some timestamps are generated in advance beyond the NOW() date time.
The desired result should be:
| Total |
|-------|
| -180 |
| 10 |
But i get
| Total |
|-------|
| -180 |
| 0 | <----- it should be 10 because of the date condition i put
thank you!
As a follow-up of same findings from Don, And your clarification of don't count after, I came up with this query... Pre-check on the date first and if after, multiply by zero, OTHERWISE, apply the +/- 1 factor.
SELECT
SUM( b.trans_value *
CASE when ( a.aoc_type = '15'
AND b.trans_type = '0'
AND DATE(b.trans_date) > DATE(NOW()) )
then 0
when ( a.aoc_type = '4'
AND b.trans_type = '0' )
OR ( a.aoc_type = '15'
AND b.trans_type = '0'
AND DATE(b.trans_date) <= DATE(NOW()) )
then -1 else 1 end ) as total
FROM
data_aoc a
LEFT JOIN data_log b
ON a.aoc_id = b.log_aoc_id
WHERE
a.client_id='1'
GROUP BY
a.aoc_name
ORDER BY
a.aoc_id asc
Also posted on SQL Fiddle
It seems to be working exactly as it should.
With the date clause I get:
Sensor 1 = -180
Sensor 2 = 0
If you break down the summing you get two rows to be summed for sensor #2
10 on 10-21 (before the date restriction so it's multiplied by -1)
and
10 on 11-25 (after the date restriction so it's multiplied by 1)
10 * -1 + 10 * 1 = 0
The sensor #2 reading is correctly 0.
I do not understand why you think it should be anything else.

Calculate time differences between multiple set of data

This is a data set in a mysql table which is related to a error log of an electronic divice.
i need to calculate the total down time.
time_stamp error_type error_status
1467820110 1 1
1467820120 2 1
1467820130 3 1
1467820140 3 0
1467820150 1 0
1467820160 2 0
1467820180 1 1
1467820185 1 0
1467820191 2 1
1467820300 2 0
1467820302 1 1
1467820404 3 1
1467820408 3 0
1467820409 1 0
error_status 1 = error occored
error_status 0 = error fixed
1st down time 1467820160 - 1467820110 = 50
2nd down time 1467820185 - 1467820180 = 5
3rd down time 1467820300 - 1467820191 = 109
4th down time 1467820409 - 1467820302 = 107
total down time = 50 + 5 + 109 + 107 = 271
How can i write a mySQL compatible SQL statement to achieve this.
Basically, you need to calculate the number of cumulative errors that have occurred. Then identify groups where the values are greater than 0. This can be done by doing a cumulative count of the number of "0"s for the cumulative errors.
There is a challenge getting the final timestamp. One trick is to get the next status 0 timestamp for the error. This acts as an "end".
Finally, an aggregation get the information for each period:
select count(*) as num_errors, max(end_timestamp) - min(timestamp)
from (select t.*,
(#grp := #grp + if(cume_errors = 0, 1, 0)) as grp
from (select t.*,
(select t2.timestamp
from t t2
where t2.error_type = t.error_type and
t2.error_status = 0 and
t2.timestamp > t.timestamp
order by t2.timestamp asc
limit 1
) as end_timestamp,
(#e := #e + if(error_status > 0, 1, -1)) as cume_errors
from t cross join
(select #e := 0) params
order by timestamp
) t cross join
(select #grp := 0) params
order by timestamp
) t
where error_status > 0
group by grp;
You can aggregate over this query to get the total period of downtime.
Here is a SQL Fiddle.
use this you will get the total
SELECT sum(IF(error_status=1,time_stamp*-1,time_stamp)) as total FROM table;
----------example----
mysql> SELECT sum(IF(error_status=1,time_stamp*-1,time_stamp)) as total FROM hh;
+-------+
| total |
+-------+
| 315 |
+-------+
1 row in set (0.06 sec)
mysql> SELECT *,(IF(error_status=1,time_stamp*-1,time_stamp)) as total FROM hh;
+------------+------------+--------------+-------------+
| time_stamp | error_type | error_status | total |
+------------+------------+--------------+-------------+
| 1467820110 | 1 | 1 | -1467820110 |
| 1467820120 | 2 | 1 | -1467820120 |
| 1467820130 | 3 | 1 | -1467820130 |
| 1467820140 | 3 | 0 | 1467820140 |
| 1467820150 | 1 | 0 | 1467820150 |
| 1467820160 | 2 | 0 | 1467820160 |
| 1467820180 | 1 | 1 | -1467820180 |
| 1467820185 | 1 | 0 | 1467820185 |
| 1467820191 | 2 | 1 | -1467820191 |
| 1467820300 | 2 | 0 | 1467820300 |
| 1467820302 | 1 | 1 | -1467820302 |
| 1467820404 | 3 | 1 | -1467820404 |
| 1467820408 | 3 | 0 | 1467820408 |
| 1467820409 | 1 | 0 | 1467820409 |

Joining and nesting queries in mysql

Currently, I'm using this nice query:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(CASE WHEN race_results.place=1 THEN 1 ELSE 0 END) AS times_won_first_place
from users
inner join race_results
where race_results.userid = users.id and race_results.place = 1
group by users.id
order by total_winnings desc
to get this
************************************************
| name | total_winnings | times_won_first_place |
| Bob | 4000 | 4 |
| John | 1000 | 1 |
************************************************
the race_results table looks like this
*******************************************
| id | raceid | userid | place | winnings |
| 1 | 1 | 1 | 1 | 1000 |
| 2 | 1 | 2 | 5 | 50 |
| 3 | 1 | 3 | 6 | 50 |
| 4 | 2 | 1 | 1 | 1000 |
| 5 | 2 | 2 | 3 | 250 |
*******************************************
I would like to include four three more columns for something like this
***************************************************************************
| name | total_winnings | total_races | 1st_place | 2nd_place | 3rd_place |
| Bob | 4000 | 5 | 4 | 0 | 0 |
| John | 1000 | 5 | 1 | 1 | 1 |
***************************************************************************
If I were to do separate queries for the new columns, I'd use
select count(raceid) from race_results where userid = 1
select count(raceid) from race_results where userid = 1 and place = 1
select count(raceid) from race_results where userid = 1 and place = 2
select count(raceid) from race_results where userid = 1 and place = 3
to do separate queries would be easy but with the existing query I had to use CASE just to get the count of times a user won 1st place. (using
count(CASE WHEN race_results.place=2 THEN 1 ELSE 0 END)
returns the same results).
How would I nest these or join them into my existing query to get what I want?
You can do it this way:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(*) AS total_races,
sum(race_results.place = 1) AS times_won_first_place ,
sum(race_results.place = 2) AS times_won_second_place,
sum(race_results.place = 3) AS times_won_third_place
from users
inner join race_results
where race_results.userid = users.id
group by users.id
order by total_winnings desc;
With ANSI standard SQL you could use case expressions inside the sum function but since MySQL (and some other databases) evaluate boolean expressions to 1 for true you can replace the case expression with the just the condition to evaluate and then just sum them.
So instead of CASE WHEN race_results.place=1 THEN 1 ELSE 0 END you can do sum(race_results.place=1) and save some space and typing :)
See this SQL Fiddle for an example.