Running total with condition and always looking at the previous value - mysql

I want to do a sequential sum in a table by taking into consideration a few extra conditions.
We need to make sure that when the sum is taken sequentially so if a id has +40 then the next sum would be 130, if the next one is +1, the sum is still 130, now if the next one is -1 then the sum has to be 129.
100 needs to be added to the sum for the first time and from there on just the count should be added depending on condition.
We need to even cap the min value of sum so it can't be less than 70
I have tried the query below but it does not seem to look at the prior value.
Example that I tried:
create table tableA (id int not null, count int not null);
insert into tableA(id, count) values(1,11), (2,21),(3, -3); -- case 1
insert into tableA(id, count) values(1,35), (2,-3); -- case 2
insert into tableA(id, count) values(1,-45),(2,67); -- case3
Query tried:
select t.id, t.count,
case when (100+(select ifnull(sum(count),0) from tableA x where x.id <= t.id)) >= 130 then 130
when (100+(select ifnull(sum(count),0) from tableA x where x.id <= t.id)) <= 70 then 70
else (100+(select ifnull(sum(count),0) from tableA x where x.id <= t.id))
end as xxxx
from tableA t;
I expect my output to look like:
Case1 Result:
id count Sum
1 11 111
2 21 130
3 -4 126
Case2 Result:
id count Sum
1 35 130
2 -3 127
Case3 Result:
id count Sum
1 -45 70
2 67 137

THIS ANSWERS THE ORIGINAL VERSION OF THE QUESTION.
I think this does what you want:
select a.*, (#sum := least(#sum + count, 130)) as "sum"
from (select a.*
from tablea a
order by a.id
) a cross join
(select #sum := 0) params;
I don't understand where the 100 is coming from. It is not part of your explanation.
Here is a db<>fiddle that illustrates how this works using 30 as the limit (which seems to be your intention).

Related

sql calculate percentage over grouped data

I have a table X like this,
student ans_status question_id
1 1 10
2 -1 10
3 1 10
4 0 10
1 -1 11
2 1 11
3 -1 11
4 -2 11
expected o/p is
10 2/3
11 1/3
etc..
Now, i want the data fro each question 10 like,
number of 1's/(total of 1's and -1's for each question)
I have tried this,
select (select count(student_id) from X
where question_id=10 and ans_status=1) / count(student_id)
from X
where question_id=10
group by ans_status
having ans_status in(1,-1).
i can do it in a nested query, by again selecting and grouping according to the status condition, but is there any way better to do this?
please note i want this for all questions in the table
You can just do:
select question_id,
avg(ans_status = 1)
from X
where ans_status in (1, -1)
group by question_id;
This uses the MySQL feature that a boolean expression is treated as an integer in a numeric context. "True" is 1 and "false" is 0, so the average turns out to be the percentage that are true.
If you want the values independently:
select question_id,
sum(ans_status = 1), count(*)
from X
where ans_status in (1, -1)
group by question_id;
Use GROUP BY for taking count of each question_id for getting count of answer_id is 1 or -1.
Query
select t.`question_id`,
t.`count_1` / t.`total_count` as `new_col` from(
select `question_id`,
sum(case `ans_status` when 1 then 1 when -1 then 1 else 0 end) as `count_1`,
count(*) as `total_count`
from `your_table_name`
group by `question_id`
)t;
Find a demo here

Mysql query that can search with a criteria of total summation of an object

hello please help me with this mysql query,
i want to select the rows that have a summation of 17 or less when the quantity columns is added together and the searched item is Pen in an ascending order based on id.
My table structure is like this:
ID quantity item
------------------
1 5 Pen
2 3 Pen
3 10 Books
4 7 Pen
5 4 Pen
6 8 Pen
7 1 Rubber
so i want my output to be like this, because when the quantity is added together for the pen that can be equal to 17, it will have the below rows
ID quantity item
------------------
1 5 Pen
2 3 Pen
4 7 Pen
5 4 Pen
Thank you in advance.
I am guessing that you have a threshold of 17 and want the maximum number of rows that do not exceed the threshold. If so, then a cumulative sum is the right approach, and variables are the most efficient method in MySQL:
select t.*
from (select t.*, (#sum := #sum + quantity) as sumq
from t cross join
(select #sum := 0) params
where item = 'Pen'
order by id
) t
where sumq <= 17;
Here's one idea... slower than Gordon's but (for the time being at least), more accurate...
SELECT DISTINCT a.*
FROM my_table a
JOIN
( SELECT x.*
, SUM(y.quantity) total
FROM my_table x
JOIN my_table y
ON y.item = x.item AND y.id <= x.id
WHERE x.item = 'pen'
GROUP
BY x.id
HAVING 17 > SUM(y.quantity)-x.quantity
AND 17 <= SUM(y.quantity)
) b
ON b.item = a.item
AND b.id >= a.id;

SQL Query to get number of times and duration of a column values reaches a specific value in a sequence

I have a table with two columns ReceivedOn(Date/Time) and Speed(int)
Sample data will look like below
ReceivedOn | Speed
----------------------------------------------
2012-11-05 06:30:00 10
2012-11-05 06:31:00 45
2012-11-05 06:32:00 48
2012-11-05 06:33:00 53
2012-11-05 06:34:00 47
2012-11-05 06:35:00 38
2012-11-05 06:36:00 22
2012-11-05 06:37:00 36
2012-11-05 06:38:00 41
2012-11-05 06:39:00 47
2012-11-05 06:40:00 49
2012-11-05 06:41:00 22
2012-11-05 06:42:00 36
I need to group the rows when speed > 40, so that the resulting output would be
StartTime | EndTime | Count
--------------------------------------------------------
2012-11-05 06:31:00 2012-11-05 06:34:00 4
2012-11-05 06:38:00 2012-11-05 06:40:00 3
StartTime to be the ReceivedOn value when the speed first crossed 40, and the EndTime value to be the ReceivedOn value when it was last over 40 in consecutive records, with the Count being the total number of consecutive records that were over 40.
I tried my best but unable to get it. Is it possible to get this with sql query?
Please suggest. Thanks in advance.
Here's a general answer to questions like these.
Detect consecutive items meeting particular criteria in a time series
Your specific case seems to be easier because you have no time gaps (said he hopefully). You are trying to find the gaps in your time sequence. In your case the gaps are defined as those items that are >= 40. So, you're looking for gaps in the sequence of events with values less than forty.
Here's a query that gives your time squence with row numbers.
SELECT #RowA := #RowA + 1 AS ROWNUM,
ReceivedOn, Speed
FROM (
SELECT ReceivedOn, Speed
FROM obs
WHERE NOT Speed >= 40
) AS A
JOIN (SELECT #RowA := 0) AS B
Now you use a some serious SQL monkey business to self-join this sequence to itself. That works like this:
SELECT B.ReceivedON + INTERVAL 1 MINUTE As StartTime,
A.ReceivedOn - INTERVAL 1 MINUTE AS EndTime,
-1 + TIMESTAMPDIFF(MINUTE, B.ReceivedOn, A.ReceivedOn) AS Count
FROM (
SELECT #RowA := #RowA + 1 AS ROWNUM,
ReceivedOn, Speed
FROM (
SELECT ReceivedOn, Speed
FROM obs
WHERE NOT Speed >= 40
) AS A
JOIN (SELECT #RowA := 0) AS B
) AS A
JOIN (
SELECT #RowB := #RowB + 1 AS ROWNUM,
ReceivedOn, Speed
FROM (
SELECT ReceivedOn, Speed
FROM obs
WHERE NOT Speed >= 40
) AS A
JOIN (SELECT #RowB := 0) AS B
) AS B ON B.ROWNUM+1 = A.ROWNUM
WHERE TIMESTAMPDIFF(MINUTE, B.ReceivedOn, A.ReceivedOn) > 1
http://sqlfiddle.com/#!2/2cb57/24/0
This looks really hairy, but it is simply a join of that first query to itself ON B.ROWNUM+1 = A.ROWNUM. That lines up that query's result set to itself offset by one row, so you can compare consecutive rows.
That gives the result you need. Notice that if your first observation is >= 40, this query will leave out the first sequence of observations.

MySQL query that gets all rows UNTIL the SUM(column) is bigger than X

I have the following data
user_id days date
88 2 2013-08-25
88 4 2013-08-23
88 18 2013-08-5
88 1 2013-08-4
88 2 2013-08-2
73 11 2013-08-2
299 4 2013-08-2
12 983 2013-08-2
I'm trying to get all recent rows (order by DATE desc) for a specific user_id , until the SUM of days column is bigger than X. For example in this case if X=7 I would get the three first rows with SUM(days)=24.
Try this. Here you will use a local variable that will count the sums in the subquery.
select
user_id,
days,
date
from
(
select
user_id,
days,
date,
#sum_days := #sum_days + days as sum_days
from
myTable
order by
date desc
) t
cross join (select #sum_days := 0) const -- resetting your #sum_days var.
where
sum_days < X -- fill a number in for X here.

MYSQL Count entries and group by two criteria with output in to a 2d table

I have a table that looks something like this;
(actual table is larger with several million rows)
Test_table
ID Day Value
=============
1 1 4
2 1 -1
3 1 27
4 1 3
5 1 -2
6 1 -5
7 1 3
8 1 1
9 1 1
10 1 Null
11 2 1
12 2 1
13 2 2
14 2 -1
15 2 -3
I want to produce a table of these two columns with the count of the number of times each entry appears, a 2d table with the day down the rows, and the values across the top with each cell containing the count of entries in that criteria like the below;
Desired output
Day Null -5 -3 -2 -1 1 2 3 4 27
==================================================================================
1 1 1 1 1 2 2 1 1
2 1 1 2 1
A query like;
select day, value, count(*) as count
from test_Table
group by day, value
Order by day asc, value desc
;
produces the data as many rows and only 3 columns... How can I get the desired output?
You can do this with conditional aggregation:
select day,
sum(value is NULL) as "NULL",
sum(value = -5) as "-5",
sum(value = -3) as "-3",
sum(value = -2) as "-2",
sum(value = -1) as "-1",
sum(value = 1) as "1",
sum(value = 2) as "2",
sum(value = 3) as "3",
sum(value = 4) as "4",
sum(value = 27) as "27"
from test_Table
group by day
Order by day asc;
Note two things. First, the column values are fixed. If you want dynamic column names, then you need to use dynamic SQL. Second, instead of blanks this will have 0 for the days with no count of a particular value.
The short answer is that it can't be done in MySQL.
The reason is that a SELECT statement has to specify the number of columns to be returned, a name and datatype for each column. And MySQL cannot dynamically generate columns to be returned for you.
The longer answer is that you would need a query of the form:
SELECT t.Day
, SUM(IF(t.value IS NULL,1,0)) AS `Null`
, SUM(IF(t.value = -5 ,1,0) AS `-5`
, SUM(IF(t.value = -3 ,1,0) AS `-3`
, ...
FROM mytable t
GROUP BY t.Day
with each column specified in the SELECT list.
One trick you can use is to use another, separate query to help write that query you need. This has to be a separate step, a separate query. To get the list of values you want returned as column headers would be of the form:
SELECT IFNULL(v.value,'Null') AS val
FROM mytable v
GROUP BY v.value
ORDER BY IF(v.value IS NULL,0,1), v.value
If you are doing this just in MySQL (and not an application), you can have MySQL help generate the required SQL text for you (using SQL to generate SQL)
SELECT CONCAT(' , SUM(IF(t.value',
IFNULL(v.value,' IS NULL',CONCAT(' = ',v.value)),
',1,0)) AS `',v.value,'`'
) AS expr
FROM mytable v
GROUP BY v.value
ORDER BY IF(v.value IS NULL,0,1), v.value
Then copy the string values returned from the expr column, paste those into an editor, and finish creating the SQL statement, like the example shown above.
The answer from Gordon shows the expression IF(col=12,1,0) can be abbreviated to col=12.
I always find myself typing that out the IF(conditional,valtrue,valfalse), but that's just the way my brain works. It's just easier for me to read.
Similarly the expression in the ORDER BY in my example...
ORDER BY IF(v.value IS NULL,0,1)
could be rewritten...
ORDER BY v.value IS NOT NULL