MySQL summary table - evaluate and adjust - mysql

I have a very simple summary table that sums up 2 fields in a table where records are collected at 15 minute intervals. So;
SELECT timevalue, SUM(value1) AS sumvalue1, SUM(value2) AS sumvalue2
FROM table
GROUP BY timevalue
returns the results i would expect;
timevalue sumvalue1 sumvalue2
-------------------------------------
16/08/2016 08:30 3000 200
16/08/2016 08:45 3200 150
16/08/2016 09:00 3100 400
16/08/2016 09:15 3300 450
16/08/2016 09:30 3400 600
My question is, is there a way to check that each sum value is never less than the previous value? and if it is return the sum from the previous timevalue? (so therefore the sumvalues are always the same or greater than the previous timevalue).
The results table should then look like this;
timevalue sumvalue1 sumvalue2
-------------------------------------
16/08/2016 08:30 3000 200
16/08/2016 08:45 3200 200
16/08/2016 09:00 3200 400
16/08/2016 09:15 3300 450
16/08/2016 09:30 3400 600
I'm guessing i need some kind of if statement? Any ideas on how to achieve this?
Many Thanks

You can do this using the user defined variable and then doing some arithmetic something as
select timevalue,sumvalue_1 as sumvalue1, sumvalue_2 as sumvalue2 from
(
select
timevalue,
if(#prev_val1 = sumvalue1 or #prev_val1 > sumvalue1,#prev_val1,sumvalue1) as sumvalue_1,
if(#prev_val2 = sumvalue2 or #prev_val2 > sumvalue2,#prev_val2,sumvalue2) as sumvalue_2,
#prev_val1 := sumvalue1,
#prev_val2 := sumvalue2
from mytable,(select #prev_val1:=0,#prev_val2:=0)x
order by timevalue
)x
order by timevalue
Here is a demo
create table mytable (
timevalue datetime,
sumvalue1 int,
sumvalue2 int
);
insert into mytable values
('2016-08-16 08:30:00',3000,200),
('2016-08-16 08:45:00',3200,150),
('2016-08-16 09:00:00',3100,400),
('2016-08-16 09:15:00',3300,450),
('2016-08-16 09:30:00',3400,600);
mysql> select * from mytable;
+---------------------+-----------+-----------+
| timevalue | sumvalue1 | sumvalue2 |
+---------------------+-----------+-----------+
| 2016-08-16 08:30:00 | 3000 | 200 |
| 2016-08-16 08:45:00 | 3200 | 150 |
| 2016-08-16 09:00:00 | 3100 | 400 |
| 2016-08-16 09:15:00 | 3300 | 450 |
| 2016-08-16 09:30:00 | 3400 | 600 |
+---------------------+-----------+-----------+
Now with the query
mysql> select timevalue,sumvalue_1 as sumvalue1, sumvalue_2 as sumvalue2 from
-> (
-> select
-> timevalue,
-> if(#prev_val1 = sumvalue1 or #prev_val1 > sumvalue1,#prev_val1,sumvalue1) as sumvalue_1,
-> if(#prev_val2 = sumvalue2 or #prev_val2 > sumvalue2,#prev_val2,sumvalue2) as sumvalue_2,
-> #prev_val1 := sumvalue1,
-> #prev_val2 := sumvalue2
-> from mytable,(select #prev_val1:=0,#prev_val2:=0)x
-> order by timevalue
-> )x
-> order by timevalue;
+---------------------+-----------+-----------+
| timevalue | sumvalue1 | sumvalue2 |
+---------------------+-----------+-----------+
| 2016-08-16 08:30:00 | 3000 | 200 |
| 2016-08-16 08:45:00 | 3200 | 200 |
| 2016-08-16 09:00:00 | 3200 | 400 |
| 2016-08-16 09:15:00 | 3300 | 450 |
| 2016-08-16 09:30:00 | 3400 | 600 |
+---------------------+-----------+-----------+

Not the prettiest example but still should work.
INSERT INTO SecondTable VALUES (
NOW(),
GREATEST(
YOUR_SUM_1, (SELECT sumvalue1 FROM FirstTable ORDER BY timevalue DESC LIMIT 1)
),
GREATEST(
YOUR_SUM_2, (SELECT sumvalue2 FROM FirstTable ORDER BY timevalue DESC LIMIT 1)
)
);

Related

how to sum of same id will put in a new column and show on each first row of that id?

i have table like below called text_book :
Ord_ID--- book_name--- amount--- reg_time
01 --- abc1 --- 100 --- 2020/4/22 09:00
01 --- abc2 --- 50 --- 2020/4/22 09:01
01 --- abc3 --- 200 --- 2020/4/22 09:02
02 --- abc1 --- 100 --- 2020/4/22 09:00
02 --- abc2 --- 50 --- 2020/4/22 09:01
|
|...
i want the output by sql like below:
Ord_ID--- book_name--- amount--- reg_time ---------Total
01 --- abc1 --- 100 --- 2020/4/22 09:00---350
01 --- abc2 --- 50 --- 2020/4/22 09:01
01 --- abc3 --- 200 --- 2020/4/22 09:02
02 --- abc1 --- 100 --- 2020/4/22 09:00---150
02 --- abc2 --- 50 --- 2020/4/22 09:01
|
|...
First generate a row number , then join to a total then decide what to print on the basis of row number
DROP TABLE IF EXISTS T;
CREATE TABLE T (Ord_ID int, book_name varchar(10), amount int, reg_time datetime);
insert into t values
(01 , 'abc1' , 100 , str_to_date('2020/04/22 09:00', '%Y/%m/%d %H:%i')),
(01 , 'abc2' , 50 , str_to_date('2020/04/22 09:01', '%Y/%m/%d %H:%i')),
(01 , 'abc3' , 200 , str_to_date('2020/04/22 09:02', '%Y/%m/%d %H:%i')),
(02 , 'abc1' , 100 , str_to_date('2020/04/22 09:00', '%Y/%m/%d %H:%i')),
(02 , 'abc2' , 50 , str_to_date('2020/04/22 09:01', '%Y/%m/%d %H:%i'));
select a.ord_id,a.book_name,a.amount,a.reg_time,
case when a.rn = 1 then a.total
else ''
end as total
from
(
select t.*, s.total
, row_number() over (partition by ord_id order by book_name) rn
from t
join
(select ord_id ,sum(amount) total from t group by ord_id) s on s.ord_id = t.ord_id
) a;
+--------+-----------+--------+---------------------+-------+
| ord_id | book_name | amount | reg_time | total |
+--------+-----------+--------+---------------------+-------+
| 1 | abc1 | 100 | 2020-04-22 09:00:00 | 350 |
| 1 | abc2 | 50 | 2020-04-22 09:01:00 | |
| 1 | abc3 | 200 | 2020-04-22 09:02:00 | |
| 2 | abc1 | 100 | 2020-04-22 09:00:00 | 150 |
| 2 | abc2 | 50 | 2020-04-22 09:01:00 | |
+--------+-----------+--------+---------------------+-------+
5 rows in set (0.002 sec)

MYSQL Update rows with duplicate value but oldest date

Tried many suggestions to get this to work, difficult to explain so below is the data I have and the result I want to achieve.
I want to update the 'Active' Column to 0 if its not the MAX Ldate.
ID | SNumber | Ldate | Active
4804 188 2015-11-17 1
4806 189 2015-11-25 1
4807 190 2015-11-25 1
4808 191 2015-11-19 1
4809 192 2015-11-19 1
4820 193 2015-11-17 1
4821 193 2016-06-08 1
4830 194 2015-11-17 1
4831 194 2016-06-08 1
4828 195 2015-11-17 1
4829 195 2016-06-08 1
ID SNumber Ldate Active
4804 188 2015-11-17 1
4806 189 2015-11-25 1
4807 190 2015-11-25 1
4808 191 2015-11-19 1
4809 192 2015-11-19 1
4820 193 2015-11-17 0
4821 193 2016-06-08 1
4830 194 2015-11-17 0
4831 194 2016-06-08 1
4828 195 2015-11-17 0
4829 195 2016-06-08 1
I can get all rows with the MAX Ldate by "select ID, SNumber, Ldate from (select * from tbl order by SNumber, Ldate desc) x group by SNumber"
Thanks for taking the time to look!
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,SNumber INT NOT NULL
,Ldate DATE NOT NULL
);
INSERT INTO my_table VALUES
(4804,188,'2015-11-17'),
(4806,189,'2015-11-25'),
(4807,190,'2015-11-25'),
(4808,191,'2015-11-19'),
(4809,192,'2015-11-19'),
(4820,193,'2015-11-17'),
(4821,193,'2016-06-08'),
(4830,194,'2015-11-17'),
(4831,194,'2016-06-08'),
(4828,195,'2015-11-17'),
(4829,195,'2016-06-08');
SELECT x.*
, COALESCE(x.ldate = y.ldate,0) active
FROM my_table x
LEFT
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate;
+------+---------+------------+--------+
| id | SNumber | Ldate | active |
+------+---------+------------+--------+
| 4804 | 188 | 2015-11-17 | 1 |
| 4806 | 189 | 2015-11-25 | 1 |
| 4807 | 190 | 2015-11-25 | 1 |
| 4808 | 191 | 2015-11-19 | 1 |
| 4809 | 192 | 2015-11-19 | 1 |
| 4820 | 193 | 2015-11-17 | 0 |
| 4821 | 193 | 2016-06-08 | 1 |
| 4830 | 194 | 2015-11-17 | 0 |
| 4831 | 194 | 2016-06-08 | 1 |
| 4828 | 195 | 2015-11-17 | 0 |
| 4829 | 195 | 2016-06-08 | 1 |
+------+---------+------------+--------+
I can't think why you'd store this, but it's easy enough to change the above to an UPDATE. It might look something like this (obviously, you'd need to alter the table design above first)...
UPDATE my_table x
LEFT
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate
SET x.active = 0
WHERE y.snumber IS NULL;
But I think I would normally use an INNER JOIN for an UPDATE, in which case it might look like this (perhaps bound up inside a transaction)...
UPDATE my_table SET active = 0;
UPDATE my_table x
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate
SET x.active = 1;

Group by date every 10 days

I have this scheme:
+----+--+--------+--------------------+
| ID | Amount | paydate |
+----+-----------+--------------------+
| 1 | 200 |2016-11-05 |
+----+-----------+--------------------+
| 2 | 3000 |2016-11-10 |
+----+-----------+--------------------+
| 3 | 2500 |2016-11-11 |
+----+-----------+--------------------+
| ID | 100 |2016-11-21 |
+----+-----------+--------------------+
| 1 | 200 |2016-11-22 |
+----+-----------+--------------------+
| 2 | 3000 |2016-11-23 |
+----+-----------+--------------------+
| 3 | 2500 |2016-11-29 |
+----+-----------+--------------------+
How can I get the total Amount grouped by every 10 days like from the first of every month to the 10th then from 11th to 20th and from 21st to the end of the month?
to be shown like this :
+-----------+------------------------+
| Amount | paydate |
+-----------+------------------------+
| 3200 |2016-11-1 to 2016-11-10 |
+-----------+------------------------+
| 2500 |2016-11-11 to 2016-11-20|
+-----------+------------------------+
| 5800 |2016-11-21 to 2016-11-31|
+-----------+------------------------+
I tried
SELECT
SUM(Amount) AS Amount,
year(Facture.paydate) AS Annee,
month(Facture.paydate) AS Mois
FROM Facture
GROUP BY year(Facture.paydate), month(serFacture.paydate)
but this does not give me the result I need.
select sum(Amount) as sum_amount
,case
when day(paydate) <= 10 then concat(DATE_FORMAT(paydate,'%Y-%m-01'),' to ',DATE_FORMAT(paydate,'%Y-%m-10'))
when day(paydate) <= 20 then concat(DATE_FORMAT(paydate,'%Y-%m-11'),' to ',DATE_FORMAT(paydate,'%Y-%m-20'))
else concat(DATE_FORMAT(paydate,'%Y-%m-21'),' to ',DATE_FORMAT(paydate,'%Y-%m-31'))
end as paydate_period
from t
group by paydate_period
;
sum_amount paydate_period
3200 2016-11-01 to 2016-11-10
2500 2016-11-11 to 2016-11-20
5800 2016-11-21 to 2016-11-31
Here is an example query:
select
case
when day(date_field) between 1 and 10 then "01 to 10"
when day(date_field) between 11 and 20 then "11 to 20"
when day(date_field) between 21 and 31 then "21 to 31"
end as the_range,
date_format(date_field, "%m%Y") as the_month,
count(*)
from
the_table
group by
the_range, the_month
order by
the_month, the_range;
You can adapt the query so you display your result the way you need.

Select multiple fields from subquery

I have the next query:
SELECT
a.Date,
(SELECT SUM(Used), SUM(Max) FROM Switch_Statistic b WHERE Date = (SELECT MAX(Date) FROM Switch_Statistic WHERE Switch_ID = b.Switch_ID AND Date <= a.Date))
FROM Switch_Statistic a
GROUP BY Date;
As you see I need to select SUM(Used), SUM(Max) from subquery. With CONCAT is not good solution!
Table schema:
ID --- Switch_ID --- Date --- Max --- Used
Some data:
1 641 2014-10-04 2 16
20 630 2014-10-04 1 7
24 634 2014-10-04 0 8
26 641 2014-10-06 2 16
32 641 2014-10-07 2 16
35 641 2014-10-08 3 16
39 641 2014-10-09 2 16
64 293 2014-10-10 1 22
...
557 38 2014-10-12 3 22
559 293 2014-10-12 1 22
563 294 2014-10-12 6 22
565 641 2014-10-12 2 16
What I need:
Example with CONCAT_WS
mysql> SELECT
a.Date,
(SELECT CONCAT_WS('/', SUM(Used), SUM(Max)) FROM Switch_Statistic b WHERE Date = (SELECT MAX(Date) FROM Switch_Statistic WHERE Switch_ID = b.Switch_ID AND Date <= a.Date)) AS Result
FROM Switch_Statistic a
GROUP BY Date;
+------------+----------+
| Date | Result |
+------------+----------+
| 2014-10-04 | 3/31 |
| 2014-10-06 | 3/31 |
| 2014-10-07 | 3/31 |
| 2014-10-08 | 4/31 |
| 2014-10-09 | 3/31 |
| 2014-10-10 | 249/1587 |
| 2014-10-11 | 354/2147 |
| 2014-10-12 | 360/2185 |
+------------+----------+
8 rows in set (0.26 sec)
Query logic:
1) Select all date's from table
2) SUM - Used and Max for current date, if Switch_ID don't have record for this date, then select the last which exists in table
Link to sqlfiddle - http://sqlfiddle.com/#!2/c3d479
You should be able to do this with just aggregation and no subqueries or joins:
SELECT date, sum(used) as used, sum(max) as max
FROM switch_statistic ss
where ss.date = (select max(date) from Switch_Statistics ss2 where ss2.Switch_id = ss.SwitchId
GROUP BY ss.date;
EDIT:
You seem to want a cumulative sum. In MySQL, this is often best done using variables:
SELECT date, used, max, (#u := #u + used) as cumeused, #m := #m + max) as cumemax
fROM (SELECT date, sum(used) as used, sum(max) as max
FROM switch_statistic ss
GROUP BY ss.date
) ss CROSS JOIN
(SELECT #u := 0, #m := 0) vars
ORDER BY date;

Still show the proper set of time even if there's no entry for that time

I have this query where it gets the average and group the values by 15 mins from 12 AM to 11:45 PM.
SELECT FROM_UNIXTIME(t_stamp/1000, '%m/%d/%Y %l:%i %p') as t_stamp,
ROUND(AVG(CASE WHEN id = '001' THEN value END),2) Value1,
ROUND(AVG(CASE WHEN id = '002' THEN value END),2) Value2,
ROUND(AVG(CASE WHEN id = '003' THEN value END),2) Value3
FROM table1
WHERE tagid IN ("001", "002", "003") and
date(from_unixtime(t_stamp/1000)) BETWEEN "2014-05-01" AND "2014-05-01"
GROUP BY DATE(from_unixtime(t_stamp/1000)), HOUR(from_unixtime(t_stamp/1000)), MINUTE(from_unixtime(t_stamp/1000)) DIV 15
The output looks like this
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:30 AM | 399 | 59 | 106
05/01/2014 12:45 AM | 499 | 59 | 112
.
.
.
05/01/2014 11:00 PM | 149 | 199 | 100
05/01/2014 11:15 PM | 599 | 93 | 123
05/01/2014 11:30 PM | 129 | 56 | 150
05/01/2014 11:45 PM | 109 | 60 | 134
It works fine but I've noticed that sometimes if there's no entry for like the time 12:30 instead of showing
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:30 AM | Null | Null | Null
05/01/2014 12:45 AM | 499 | 59 | 112
It will show the set of time like this:
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:33 AM | 122 | 141 | 234
05/01/2014 12:45 AM | 499 | 59 | 112
What I would like to happen is when there's no time for that 15 min group it will still show the proper set of time and then just show null on the column values. The output I would like is like this:
t_stamp | Value1 | Value2 | Value3
05/01/2014 12:00 AM | 199 | 99 | 100
05/01/2014 12:15 AM | 299 | 19 | 140
05/01/2014 12:30 AM | Null | Null | Null
05/01/2014 12:45 AM | 499 | 59 | 112
How can I do this?
Thank You.
You need a table that's a source of cardinal numbers as a start for this. For the moment let's assume it exists, and it's called cardinal.
Then, you need to create a query (a virtual table) that will return rows with timestamps every fifteen minutes, starting with the earliest relevant timestamp and ending with the latest. Here's how to do that for your query.
SELECT '2014-05-01' + INTERVAL (cardinal.n * 15) MINUTE as t_stamp
FROM cardinal
WHERE cardinal.n <= 24*4
Then you need to JOIN that virtual table to your existing query, as follows
SELECT DATE_FORMAT(t_stamp.t_stamp, '%m/%d/%Y %l:%i %p') t_stamp,
ROUND(AVG(CASE WHEN id = '001' THEN value END),2) Value1,
ROUND(AVG(CASE WHEN id = '002' THEN value END),2) Value2,
ROUND(AVG(CASE WHEN id = '003' THEN value END),2) Value3
FROM table1 AS t
LEFT JOIN (
SELECT '2014-05-01' + INTERVAL (cardinal.n * 15) MINUTE as t_stamp
FROM cardinal
WHERE cardinal.n <= 24*4
) AS t_stamp
ON t_stamp.t_stamp = FROM_UNIXTIME(t.t_stamp/1000)
WHERE tagid IN ("001", "002", "003")
AND date(from_unixtime(t_stamp/1000)) BETWEEN "2014-05-01" AND "2014-05-01"
GROUP BY DATE(from_unixtime(t_stamp/1000)),
HOUR(from_unixtime(t_stamp/1000)),
MINUTE(from_unixtime(t_stamp/1000)) DIV 15
Notice that the LEFT JOIN makes sure the rows will NULL values from your original query get included in the result set.
Now, where does this magical cardinal table come from?
You can generate it as two views, like this. This particular view generates numbers from 0 to 100 000, which is more than enough for quarters of hours for a year.
CREATE OR REPLACE VIEW cardinal10 AS
SELECT 0 AS N UNION
SELECT 1 AS N UNION
SELECT 2 AS N UNION
SELECT 3 AS N UNION
SELECT 4 AS N UNION
SELECT 5 AS N UNION
SELECT 6 AS N UNION
SELECT 7 AS N UNION
SELECT 8 AS N UNION
SELECT 9 AS N;
CREATE OR REPLACE VIEW cardinal AS
SELECT A.N + 10*(B.N + 10*(C.N + 10*(D.N + 10*(E.N)))) AS N
FROM cardinal10 A,cardinal10 B,cardinal10 C,
cardinal10 D,cardinal10 E;
Here's a writeup on the topic.
http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/