MYSQL condition inside case not working - mysql

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.

Related

Get return for the latest day

I am running a mysql - 10.1.39-MariaDB - mariadb.org binary- database.
I am having the following table:
| id | date | product_name | close |
|----|---------------------|--------------|-------|
| 1 | 2019-08-07 00:00:00 | Product 1 | 806 |
| 2 | 2019-08-06 00:00:00 | Product 1 | 982 |
| 3 | 2019-08-05 00:00:00 | Product 1 | 64 |
| 4 | 2019-08-07 00:00:00 | Product 2 | 874 |
| 5 | 2019-08-06 00:00:00 | Product 2 | 739 |
| 6 | 2019-08-05 00:00:00 | Product 2 | 555 |
| 7 | 2019-08-07 00:00:00 | Product 3 | 762 |
| 8 | 2019-08-06 00:00:00 | Product 3 | 955 |
| 9 | 2019-08-05 00:00:00 | Product 3 | 573 |
I want to get the following output:
| id | date | product_name | close | daily_return |
|----|---------------------|--------------|-------|--------------|
| 4 | 2019-08-07 00:00:00 | Product 2 | 874 | 0,182679296 |
| 1 | 2019-08-07 00:00:00 | Product 1 | 806 | -0,179226069 |
Basically I want ot get the TOP 2 products with the highest return. Whereas return is calculated by (close_currentDay - close_previousDay)/close_previousDay for each product.
I tried the following:
SELECT
*,
(
CLOSE -(
SELECT
(t2.close)
FROM
prices t2
WHERE
t2.date < t1.date
ORDER BY
t2.date
DESC
LIMIT 1
)
) /(
SELECT
(t2.close)
FROM
prices t2
WHERE
t2.date < t1.date
ORDER BY
t2.date
DESC
LIMIT 1
) AS daily_return
FROM
prices t1
WHERE DATE >= DATE(NOW()) - INTERVAL 1 DAY
Which gives me the return for each product_name.
How to get the last product_name and sort this by the highest daily_return?
Problem Statement: Find the top 2 products with the highest returns on the latest date i.e. max date in the table.
Solution:
If you have an index on date field, it would be super fast.
Scans table only once and also uses date filter(index would allow MySQL to only process rows of given date range only.
A user-defined variable #old_close is used to find the return. Note here we need sorted data based on product and date.
SELECT *
FROM (
SELECT
prices.*,
CAST((`close` - #old_close) / #old_close AS DECIMAL(20, 10)) AS daily_return, -- Use #old_case, currently it has value of old row, next column will set it to current close value.
#old_close:= `close` -- Set #old_close to close value of this row, so it can be used in next row
FROM prices
INNER JOIN (
SELECT
DATE(MAX(`date`)) - INTERVAL 1 DAY AS date_from, -- if you're not sure whether you have date before latest date or not, can keep date before 1/2/3 day.
#old_close:= 0 as o_c
FROM prices
) AS t ON prices.date >= t.date_from
ORDER BY product_name, `date` ASC
) AS tt
ORDER BY `date` DESC, daily_return DESC
LIMIT 2;
Another version which doesn't depend on this date parameter.
SELECT *
FROM (
SELECT
prices.*,
CAST((`close` - #old_close) / #old_close AS DECIMAL(20, 10)) AS daily_return, -- Use #old_case, currently it has value of old row, next column will set it to current close value.
#old_close:= `close` -- Set #old_close to close value of this row, so it can be used in next row
FROM prices,
(SELECT #old_close:= 0 as o_c) AS t
ORDER BY product_name, `date` ASC
) AS tt
ORDER BY `date` DESC, daily_return DESC
LIMIT 2
You can do it with a self join:
select
p.*,
cast((p.close - pp.close) / pp.close as decimal(20, 10)) as daily_return
from prices p left join prices pp
on p.product_name = pp.product_name
and pp.date = date_add(p.date, interval -1 day)
order by p.date desc, daily_return desc, p.product_name
limit 2
See the demo.
Results:
| id | date | product_name | close | daily_return |
| --- | ------------------- | ------------ | ----- | ------------ |
| 4 | 2019-08-07 00:00:00 | Product 2 | 874 | 0.182679296 |
| 1 | 2019-08-07 00:00:00 | Product 1 | 806 | -0.179226069 |

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.

Calculating difference on datetime row betwen rows on the same table

I have table that holds records with tasks, status and time when triggered:
Table tblwork:
+-------------+------------+---------------------+-----+
| task | status | stime | id |
+-------------+------------+---------------------+-----+
| A | 1 | 2018-03-07 20:00:00 | 1 |
| A | 2 | 2018-03-07 20:30:00 | 2 |
| A | 1 | 2018-03-07 21:00:00 | 3 |
| A | 3 | 2018-03-07 21:30:00 | 4 |
| B | 1 | 2018-03-07 22:30:00 | 5 |
| B | 3 | 2018-03-07 23:30:00 | 6 |
+-------------+------------+---------------------+-----+
Status 1 means start, 2 - pause, 3 - end
Then I need to calculate how much time is spent for each task excluding pause (status = 2). This is how I do it:
SELECT t1.id, t1.task,
SUM(timestampdiff(second,IFNULL(
(SELECT MAX(t2.stime) FROM tblwork t2 WHERE t2.task='B' AND t2.stime< t1.stime) ,t1.stime),t1.stime)) myTimeDiffSeconds
FROM tblwork t1
WHERE t1.task='B' and (t1.status = 1 or t1.status = 3);
Now I want to get table for all tasks
SELECT t1.id, t1.task,
SUM(timestampdiff(second,IFNULL(
(SELECT MAX(t2.stime) FROM tblwork t2 WHERE t2.stime< t1.stime) ,t1.stime),t1.stime)) myTimeDiffSeconds
FROM tblwork t1
WHERE (t1.status = 1 or t1.status = 3) GROUP BY t1.taks
I get this result:
+-------------+------------+---------------------+
| task | id | mytimedifference |
+-------------+------------+---------------------+
| A | 1 | 3600 |
| B | 3 | 2421217 |
+-------------+------------+---------------------+
Calculation for A is correct B is wrong, it should be 3600 second but i don't understand why.
Assuming there is always a start for each pause and end, wouldn't something like this be more direct?
SELECT t.task
, SUM(TO_SECONDS(t.stime)
* CASE WHEN t.status IN (1) THEN -1
WHEN t.status IN (2, 3) THEN 1
ELSE 0
END
) AS totalTimeSecs
FROM tblwork AS task
GROUP BY t.task
I'm not quite sure offhand how big the values that come out of TO_SECONDS() are for current timestamps; but if they are an issue when being summed, if could be changed to
, SUM((TO_SECONDS(t.stime) - some_constant_just_before_or_at_your_earliest_seconds)
* CASE WHEN t.status IN (1) THEN -1
WHEN t.status IN (2, 3) THEN 1
ELSE 0
END
) AS totalTimeSecs
You can detect "abnormal" data by adding the following to the select expression list
, CASE WHEN SUM(CASE
WHEN t.status IN (1) THEN -1
WHEN t.status IN (2, 3) THEN 1
ELSE 0 END
) = 0
THEN 'OK'
ELSE 'ABNORMAL'
END AS integrityCheck
Note: any "unclosed" intervals will be marked as abnormal; without much more complicated and expensive start and end checking for intervals to differentiate "open" from "invalid", it's probably the best that can be done. The sum used for additonal "integrityCheck" equaling -1 might hint at an open ended interval, but could also indicate an erroneous double-start.

How to count in a range of result in 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

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