select sum of max values from multiple incrementing sequences - mysql

I want to calculate a sum of the max values from sequence of increment values.
for this data set:
time_stamp count
1467820429 6 *
1467820428 5
1467820427 4
1467820426 3
1467820416 2
1467820415 1
1467820413 0
1467820412 3 *
1467820411 2
1467820409 1
1467820408 0
1467820405 1 *
1467820404 0
1467820400 5 *
answer = 6 + 3 + 1 + 5 = 15
how can i write a MySQL compatible SQL statement to acheve this

As I mentioned in comments there is no efficient way to do this in Mysql atleast to my knowledge
Try this
SELECT Sum(CASE
WHEN `count` >= prev_cnt THEN `count`
ELSE 0
END)
FROM (SELECT *,
IFnull((SELECT `count`
FROM yourtable b
WHERE a.`time_stamp` < b.`time_stamp`
ORDER BY `time_stamp` LIMIT 1), `count`) AS prev_cnt
FROM yourtable a) c

you can get it in following method
mysql> select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,(SELECT #curRank := 0) r;
+------------+-------+------+
| time_stamp | count | rank |
+------------+-------+------+
| 1467820429 | 6 | 1 |
| 1467820428 | 5 | 2 |
| 1467820427 | 4 | 3 |
| 1467820426 | 3 | 4 |
| 1467820415 | 2 | 5 |
| 1467820415 | 1 | 6 |
| 1467820413 | 0 | 0 |
| 1467820412 | 3 | 1 |
| 1467820411 | 2 | 2 |
| 1467820409 | 1 | 3 |
| 1467820408 | 0 | 0 |
| 1467820405 | 1 | 1 |
| 1467820404 | 0 | 0 |
| 1467820408 | 5 | 1 |
+------------+-------+------+
14 rows in set (0.00 sec)
mysql> SELECT * FROM (select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,(SELECT #curRank := 0) r) t WHERE rank=1;
+------------+-------+------+
| time_stamp | count | rank |
+------------+-------+------+
| 1467820408 | 5 | 1 |
| 1467820412 | 3 | 1 |
| 1467820429 | 6 | 1 |
| 1467820405 | 1 | 1 |
+------------+-------+------+
4 rows in set (0.00 sec)
mysql> SELECT sum(count) as total FROM
(select time_stamp,count,if (count=0,#curRank :=0,#curRank := #curRank + 1) as rank from ff,
(SELECT #curRank := 0) r) t WHERE rank=1;
+-------+
| total |
+-------+
| 15 |
+-------+
1 row in set (0.00 sec)
you can get it with simple inner query

SELECT SUM(a.cnt)
FROM
( SELECT x.*
, MIN(y.time_stamp) next
FROM my_table x
LEFT
JOIN my_table y
ON y.time_stamp > x.time_stamp
GROUP
BY x.time_stamp
) a
LEFT
JOIN my_table b
ON b.time_stamp = a.next
AND b.cnt > a.cnt
WHERE b.cnt IS NULL;

You need to identify when a value shifts. One way to get the previous value uses variables:
select sum(count)
from (select t.*,
(if((#old_c := #c) is null, 0, -- never happens
if((#c := count) is not null, #old_c, #old_c)
)
) as prev_count
from t cross join
(select #c := -1) params
order by time_stamp
) t
where prev_count >= count;
The expression for getting the previous count is a bit complicated. MySQL does not guarantee the order of evaluation of expressions, so the assignment of the new value of count and returning the old value needs to be in a single expression.

You need GROUP BY and HAVING, like this:
select sum ( count )
from table
group by time_stamp
having count = max(count)

Very simple solution :
1- take the lag of the column time_stamp
2- take the difference of orif time_stamp column and the lag column
3- sum the values of count after filtering out the records for -1
+------------+-------+------+-----------------+-------+
| a | b | c | d | a-d |
| time_stamp | count | flag | lag_time_stamp | diff |
| 1467820429 | 6 | * | nulll | null |
| 1467820428 | 5 | | 1467820429 | -1 |
| 1467820427 | 4 | | 1467820428 | -1 |
| 1467820426 | 3 | | 1467820427 | -1 |
| 1467820416 | 2 | * | 1467820426 | -10 |
| 1467820415 | 1 | | 1467820416 | -1 |
| 1467820413 | 3 | * | 1467820415 | -2 |
| 1467820412 | 3 | | 1467820413 | -1 |
| 1467820411 | 2 | | 1467820412 | -1 |
| 1467820409 | 1 | * | 1467820411 | -2 |
| 1467820408 | 0 | | 1467820409 | -1 |
| 1467820405 | 1 | * | 1467820408 | -3 |
| 1467820404 | 0 | | 1467820405 | -1 |
| 1467820400 | 5 | * | 1467820404 | -4 |
+------------+-------+------+-----------------+-------+
--sum the values of the table that we got after filtering the records for -1
+------------+-------+
| time_stamp | count |
+------------+-------+
| 1467820429 | 6 |
| 1467820416 | 2 |
| 1467820413 | 3 |
| 1467820409 | 1 |
| 1467820405 | 1 |
| 1467820400 | 5 |
+------------+-------+

Related

Count occurences in Mysql

Let's say, in given num_table, there is a column, in which only numbers from 1 to 35 are stored.
Code for count nums in last 25rows is:
select num, count(*)
from (select C_1 as num from num_table order by id desc limit 25) n
group by num
order by num asc;
Result:
| num | count(*) |
|------|----------|
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 10 | 1 |
| 11 | 1 |
| 12 | 1 |
| 15 | 1 |
| 16 | 2 |
| 17 | 1 |
| 20 | 1 |
| 21 | 1 |
| 22 | 1 |
| 23 | 1 |
| 25 | 1 |
| 28 | 2 |
| 29 | 2 |
| 30 | 1 |
| 32 | 2 |
|------|----------|
How to get a result, where nums from 1 to 35 - which occured 0 times within last 25 rows - will be also displayed?
Example of desired result:
| num | count(*) |
|------|----------|
| 1 | 0 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 6 | 0 |
| 7 | 0 |
| 8 | 0 |
| 9 | 0 |
| 10 | 1 |
| ... | ... |
| 35 | 0 |
Maybe the quickest way is to make your existing query as sub-query and LEFT JOIN your num_table with it like :
SELECT A.C_1, IFNULL(cnt,0) total_count
FROM num_table A
LEFT JOIN
(SELECT num, COUNT(*) cnt
FROM (SELECT C_1 AS num FROM num_table ORDER BY id DESC LIMIT 25) n
GROUP BY num) B
ON A.C_1=B.num
GROUP BY A.C_1, cnt
ORDER BY A.C_1 ASC;
Here's a fiddle for reference:
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=3ced94d698fd8a55a8ad07a9d3b42f3d
And by the way, the current result you're showing is only 24 rows despite you did LIMIT 25 in the first sub-query. So in my example fiddle, the result is slightly different.
Here is another way to solve your problem.
In this solution, first, you need a table with numbers between 1 and 35, but only for the query, so then you can left join (because with a left join you can have also 0 counter values) it with your existent num_table.
You can do it like this:
WITH RECURSIVE numbers(id) AS (
SELECT 1 as id
UNION ALL
SELECT id+1 FROM numbers WHERE id < 35
)
SELECT numbers.id AS num, count(nt.id) AS total
FROM numbers
LEFT JOIN (SELECT C_1 FROM num_table ORDER BY id DESC LIMIT 25) nt ON (nt.C_1 = numbers.id)
GROUP BY numbers.id

mysql sequence number by value coloumn (query UPDATE)

example:
I have a table with the columns
______________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 4 |
| 2 | a | 5 |
| 3 | a | 6 |
| 3 | a | 7 |
| 3 | a | 8 |
how to query it, so sequence number look like this
_____________________
|field_id|Code|seq_num|
| 1 | a | 1 |
| 1 | a | 2 |
| 1 | a | 3 |
| 2 | a | 1 |
| 2 | a | 2 |
| 3 | a | 1 |
| 3 | a | 2 |
| 3 | a | 3 |
please help!!
One method is to get the minimum sequence for the field:
select t.field_id, t.code,
(seq_num - min_seqnum + 1) as seqnum
from t join
(select field_id, min(seq_num) as min_seq_num
from t
group by field_id
) f
on t.field_id = f.field_id;
You can also do this using variables, if you don't trust the current sequence numbers to have no gaps:
select . . .,
(#rn := if(#f = field_id, #rn + 1,
if(#f := field_id, 1, 1)
)
) as seq_no
from (select t.*
from t
order by field_id, seq_no
) t cross join
(select #f := '', #rn := 0) params;

Order by logic and get mixed results?

I have this table structure
id | status
-----+------+
1 | a |
2 | b |
3 | b |
4 | b |
5 | a |
6 | a |
7 | b |
8 | a |
I have two statuses "a" and "b".
I need to order this by logic that says this: for each two "a", show one "b".
So something like this (the "a" is more important, so if there are a lot of "b"s they will just be left in the end)
id | status
-----+------+
1 | a |
5 | a |
2 | b |
6 | a |
8 | a |
3 | b |
4 | b |
7 | b |
Is there a way to do this?
select id
,status
from (select status
,id
,#prev_status := #status
,#status := status
,#rn := case when #prev_status = status
then #rn + 1
else 1
end as rn
from mytable t1,(select #status:=null,#rn:=0) x
order by status
,id
) t
order by floor((rn-1) / case status when 'a' then 2 else 1 end)
,case status when 'a' then 1 else 2 end
,rn
;
+----+--------+
| id | status |
+----+--------+
| 1 | a |
| 5 | a |
| 2 | b |
| 6 | a |
| 8 | a |
| 3 | b |
| 4 | b |
| 7 | b |
+----+--------+
This will help you to understand the solution:
(group_id = floor((rn-1) / case status when 'a' then 2 else 1 end))
+--------+----+----+----------+
| status | id | rn | group_id |
+--------+----+----+----------+
| a | 1 | 1 | 0 |
| a | 5 | 2 | 0 |
| a | 6 | 3 | 1 |
| a | 8 | 4 | 1 |
| b | 2 | 1 | 0 |
| b | 3 | 2 | 1 |
| b | 4 | 3 | 2 |
| b | 7 | 4 | 3 |
+--------+----+----+----------+
Use variables to enumerate the values and then some simple logic:
select id, status
from (select t.*,
(#rn := if(#s = status, #rn + 1,
if(#s := status, 1, 1)
)
) as rn
from t cross join
(select #rn := 0, #s := '') params
where status in ('a', 'b')
order by status, id
) ab
order by (case when status = a then floor( (rn - 1) / 2) else (rn - 1) end),
status, id;

SQL Query Conditional accumulation

it is possible to display accumulated data, resetting the count based on a condition?
I would like to create a script to accumulate if there is value 1 in cell number, but if another value the count should be restarted. Something like what is displayed in the column cumulative_with_condition.
+----+------------+--------+
| id | release | number |
+----+------------+--------+
| 1 | 2016-07-08 | 4 |
| 2 | 2016-07-09 | 1 |
| 3 | 2016-07-10 | 1 |
| 4 | 2016-07-12 | 2 |
| 5 | 2016-07-13 | 1 |
| 6 | 2016-07-14 | 1 |
| 7 | 2016-07-15 | 1 |
| 8 | 2016-07-16 | 2-3 |
| 9 | 2016-07-17 | 3 |
| 10 | 2016-07-18 | 1 |
+----+------------+--------+
select * from version where id > 1 and id < 9;
+----+------------+--------+---------------------------+
| id | release | number | cumulative_with_condition |
+----+------------+--------+---------------------------+
| 2 | 2016-07-09 | 1 | 1 |
| 3 | 2016-07-10 | 1 | 2 |
| 4 | 2016-07-12 | 2 | 0 |
| 5 | 2016-07-13 | 1 | 1 |
| 6 | 2016-07-14 | 1 | 2 |
| 7 | 2016-07-15 | 1 | 3 |
| 8 | 2016-07-16 | 2-3 | 0 |
+----+------------+--------+---------------------------+
You want something like row_number() (not exactly, but like that). You can do that using variables:
select t.*,
(#rn := if(number = 1, #rn + 1,
if(#n := number, 0, 0)
)
) as cumulative_with_condition
from t cross join
(select #n := '', #rn := 0) params
order by t.id;
As an alternative to using user variables, as demonstrated by Gordon Linoff, in this case it's also possible to self-join, group and count:
SELECT t.id, t.release, t.number, COUNT(version.id) AS cumulative_with_condition
FROM version RIGHT JOIN (
SELECT highs.*, MAX(lows.id) min
FROM version lows RIGHT JOIN version highs ON lows.id <= highs.id
WHERE lows.number <> '1'
GROUP BY highs.id
) t ON version.id > t.min AND version.id <= t.id
WHERE t.id > 1 AND t.id < 9
GROUP BY t.id
See it on sqlfiddle.
But, frankly, neither approach is particularly elegant—as I commented previously, you're probably best off implementing this within your application code.

Update innerquery result

I have a query and a result as follows.
In the database NULL and 0 represent the same meaning.
Now I want a counter based on Null+0 or 1
Eg:in the following example I want the result like this:
IsVirtual Category counter
NULL+0 3 343+8 = (351 is Total)
Query
select * from
(
Select IsVirtual, Category, count(*) as counter
from [Hardware]
group by IsVirtual, Category
) innercat
Output
+-----------+----------+---------+
| IsVirtual | Category | counter |
+-----------+----------+---------+
| NULL | 3 | 343 |
| 0 | 3 | 8 |
| 1 | 2 | 1 |
| 0 | 1 | 1 |
| NULL | 6 | 119 |
| 0 | 4 | 1 |
| NULL | 1 | 70 |
| 0 | 5 | 9 |
| NULL | 4 | 54 |
| 0 | 2 | 2 |
| NULL | 5 | 41 |
| NULL | 2 | 112 |
| 1 | 1 | 5 |
+-----------+----------+---------+
I think you want this :
SELECT COALESCE(IsVirtual, 0) as [IsVirtual],
Category,
Count(*) as [Counter]
FROM yourtable
GROUP BY COALESCE(IsVirtual, 0),Category
This will give you expected result without using subquery.
try with this
select * from (
Select CASE ISNULL(IsVirtual,0)
WHEN 0 Then 'NULL + 0'
ELSE IsVirtual
END AS IsVirtual, Category, count(*) as counter from [Hardware] group by ISNULL(IsVirtual,0), Category
)innercat
You can also do the same thing by using MAX function
This might help you.
SELECT
max(IsVirtual) as IsVirtual,
Category,
Count(*) as Counter
FROM
yourtable
GROUP BY
Category