Calculate periods and average sum where balance was positive - mysql

I'm stuck with window function.
I have this table called task:
user_id VARCHAR
date DATE
balance INTEGER
+---------+------------+---------+
| user_id | date | balance |
+---------+------------+---------+
| 1 | 03.04.2020 | 0 |
| 1 | 04.04.2020 | 265 |
| 1 | 05.04.2020 | 140 |
| 1 | 06.04.2020 | 70 |
| 1 | 07.04.2020 | 0 |
| 2 | 03.04.2020 | 535 |
| 2 | 04.04.2020 | 115 |
| 2 | 05.04.2020 | 0 |
| 2 | 06.04.2020 | 0 |
| 2 | 07.04.2020 | 694 |
+---------+------------+---------+
I'm trying to calculate all the periods where balance was constantly positive.
So the output table should look like this:
+---------+------------+------------+-------------+-------------+
| user_id | start_date | end_date | avg_balance | date_length |
+---------+------------+------------+-------------+-------------+
| 1 | 04.04.2020 | 06.04.2020 | 158.3 | 3 |
| 2 | 03.04.2020 | 04.04.2020 | 325 | 2 |
| 2 | 07.04.2020 | 07.04.2020 | 694 | 1 |
+---------+------------+------------+-------------+-------------+
I've tried to implement the window function but got stuck.

Assign periods by counting the number of zeros before. Then aggregate:
select user_id, min(date), max(date), avg(balance), count(*) as date_length
from (select t.*,
sum( balance = 0 ) over (partition by user_id order by date) as grp
from t
) t
where balance > 0
group by user_id, grp;
Here is a db<>fiddle.

Related

Calculating difference between min value and each row's value

I have a database table which has a person's id, name, and time (in milliseconds, stored as an int). For example:
| id | name | totalTime |
| --- | ------ | --------- |
| 1 | Bob | 16280 |
| 2 | Andy | 17210 |
| 3 | Bill | 15320 |
| 4 | Matt | 14440 |
| 5 | Steven | 17570 |
| 6 | Tom | NULL |
| 7 | Angus | 17210 |
| 8 | Will | NULL |
| 9 | Jack | 17410 |
| 10 | Alex | 16830 |
Not necessarily all people have a time (thus the nulls).
I would like to have another two columns - one which shows the rank/position of each person, and another which shows the difference in time (milliseconds) between the best (i.e. minimum) time and each row's time.
I have managed to write a MySQL 8.x query which does the ranks:
SELECT id, name, totalTime,
(CASE WHEN totalTime IS NOT NULL THEN RANK() OVER ( PARTITION BY (CASE WHEN totalTime IS NOT NULL THEN 1 ELSE 0 END) ORDER BY totalTime ) END) totalRank
FROM results
ORDER BY -totalRank DESC;
...and outputs this:
| id | name | totalTime | totalRank |
| --- | ------ | --------- | --------- |
| 4 | Matt | 14440 | 1 |
| 3 | Bill | 15320 | 2 |
| 1 | Bob | 16280 | 3 |
| 10 | Alex | 16830 | 4 |
| 2 | Andy | 17210 | 5 |
| 7 | Angus | 17210 | 5 |
| 9 | Jack | 17410 | 7 |
| 5 | Steven | 17570 | 8 |
| 6 | Tom | NULL | NULL |
| 8 | Will | NULL | NULL |
...but have not been able to figure out the SQL to add another column with the time difference.
Below is an example of what I would like, but can't figure out how to do:
| id | name | totalTime | totalRank | difference |
| --- | ------ | --------- | --------- | ---------- |
| 4 | Matt | 14440 | 1 | 0 |
| 3 | Bill | 15320 | 2 | 880 |
| 1 | Bob | 16280 | 3 | 1840 |
| 10 | Alex | 16830 | 4 | 2390 |
| 2 | Andy | 17210 | 5 | 2770 |
| 7 | Angus | 17210 | 5 | 2770 |
| 9 | Jack | 17410 | 7 | 2970 |
| 5 | Steven | 17570 | 8 | 3130 |
| 6 | Tom | NULL | NULL | NULL |
| 8 | Will | NULL | NULL | NULL |
I have this available as a DB Fiddle: https://www.db-fiddle.com/f/gQvSeij2EKSufYp9VjbDav/0
Thanks in advance for any help!
You can use a CTE to get the min totalTime and use it to calculate the difference:
WITH cte as (SELECT MIN(totalTime) minTotalTime FROM results)
SELECT id, name, totalTime,
CASE WHEN totalTime IS NOT NULL
THEN RANK() OVER (PARTITION BY (
CASE
WHEN totalTime IS NOT NULL THEN 1
ELSE 0
END
) ORDER BY totalTime)
END totalRank,
totalTime - (SELECT minTotalTime from cte) difference
FROM results
ORDER BY -totalRank DESC;
See the demo.
Results:
| id | name | totalTime | totalRank | difference |
| --- | ------ | --------- | --------- | ---------- |
| 4 | Matt | 14440 | 1 | 0 |
| 3 | Bill | 15320 | 2 | 880 |
| 1 | Bob | 16280 | 3 | 1840 |
| 10 | Alex | 16830 | 4 | 2390 |
| 2 | Andy | 17210 | 5 | 2770 |
| 7 | Angus | 17210 | 5 | 2770 |
| 9 | Jack | 17410 | 7 | 2970 |
| 5 | Steven | 17570 | 8 | 3130 |
| 6 | Tom | | | |
| 8 | Will | | | |
Add min() window function
SELECT id, name, totalTime,
(CASE WHEN totalTime IS NOT NULL THEN RANK() OVER ( PARTITION BY (CASE WHEN totalTime IS NOT NULL THEN 1 ELSE 0 END) ORDER BY totalTime ) END) totalRank
,totaltime - min(totaltime) over() diff
FROM results
ORDER BY -totalRank DESC;
SELECT subtable.id,
subtable.NAME,
subtable.totalTime,
subtable.diff,
IIF(subtable.totalTime IS NULL,NULL,subtable.rowno) as bisi
FROM (
select *,
ROW_NUMBER() OVER (ORDER BY totalTime desc) as rowno,
totalTime -
(
select min(rst.totalTime)
from results rst) as diff
from results) subtable;
I would do this way in MS-SQL or alternatively in MYSQL
SELECT subtable.id,
subtable.NAME,
subtable.totalTime,
subtable.diff,
IF (subtable.totalTime IS NULL, NULL, subtable.rowno) as bisi
FROM (
select *,
ROW_NUMBER() OVER (ORDER BY totalTime desc) as rowno,
totalTime -
(
select min(rst.totalTime)
from results rst) as diff
from results) subtable;
Serg's answer is correct. I would write it as:
SELECT id, name, totalTime,
(CASE WHEN totalTime IS NOT NULL
THEN RANK() OVER (PARTITION BY (totalTime IS NULL) ORDER BY totalTime)
END) as totalRank,
totaltime - MIN(totaltime) OVER() as diff
FROM results
ORDER BY (totalTime IS NOT NULL) DESC, totalRank;
The differences are:
Simplifying the PARTITION BY. You use CASE, but MySQL conveniently treats booleans as "real" values.
Expressing the ORDER BY in a more intuitive fashion.

How to get record for highest value based on type and 2 columns

So I have a table here called my_table
| userid | gender | year | money |
| ------ | ------ | ---- | ----- |
| 1 | B | 12 | 50 |
| 1 | B | 24 | 150 |
| 1 | B | 36 | 5 |
| 1 | G | 0 | 0 |
| 1 | G | 12 | 120 |
| 1 | G | 48 | 120 |
| 2 | B | 12 | 23 |
| 2 | B | 24 | 56 |
| 2 | G | 36 | 23 |
| 3 | G | 0 | 234 |
| 3 | G | 12 | 34 |
| 4 | G | 0 | 0 |
I want to return the row for the highest money available based on the gender.
So for example, the return table for gender B will be like
| userid | gender | year | money |
| ------ | ------ | ---- | ----- |
| 1 | B | 24 | 150 |
| 2 | B | 24 | 56 |
And the return table for gender G will be like
| userid | gender | year | money |
| ------ | ------ | ---- | ----- |
| 1 | G | 12 | 120 |
| 1 | G | 48 | 120 |
| 2 | G | 36 | 23 |
| 3 | G | 0 | 234 |
| 4 | G | 0 | 0 |
But since the table for gender G has duplicated value of money, I want it to return the row with the highest year.
Expected table for gender G is
| userid | gender | year | money |
| ------ | ------ | ---- | ----- |
| 1 | G | 48 | 120 |
| 2 | G | 36 | 23 |
| 3 | G | 0 | 234 |
| 4 | G | 0 | 0 |
What I have tried : http://sqlfiddle.com/#!9/5433cbc/2
I am able to get the table for gender B but not gender G.
What I am trying to achieve is to generate a table joining both of the gender table above into one. Example,
| userid | year_b | max_money_for_b | year_g | max_money_for_g |
| ------ | ------ | --------------- | ------ | --------------- |
| 1 | 24 | 150 | 48 | 120 |
| 2 | 24 | 56 | 36 | 23 |
| 3 | null | null | 0 | 234 |
| 4 | null | null | 0 | 0 |
I think a correlated subquery is the simplest approach in MySQL:
select t.*
from my_table t
where t.gender = 'G' and
(t.year, t.money) = (select t2.year, t2.money
from my_table t2
where t2.userid = t.userid and
t2.gender = t.gender
order by t2.money desc, t2.year desc
limit 1
);
As below, I think you would get the right results.
select
m.userid,
m.gender,
tmp1.m_money as money,
tmp2.m_year as year
from
my_table m
join
(
select
gender,
userid,
max(money) as m_money
from
my_table
group by
gender,
userid
) tmp1 on m.gender = tmp1.gender and m.userid=tmp1.userid and m.money=tmp1.m_money
join
(
select
gender,
userid,
money,
max(year) as m_year
from
my_table
group by
gender,
userid,
money
) tmp2 on m.gender = tmp2.gender and m.userid=tmp2.userid and m.money=tmp2.money and m.year=tmp2.m_year
where m.gender='G'
order by m.gender,m.userid
If you use the PostgreSQL or MySQL which version is greater than 8.0, things will be simplier, you can just use the window function to implement your requirement.As below:
select * from
(
select
user_id,
gender,
money,
year,
row_number() over(partition by userid,gender order by money desc,year desc) as sort
from
my_table
) tmp where sort = 1
remove the condition gender = 'G' from your where condition
DEMO
select t.* from my_table t where
not exists (
select 1 from my_table where userid = t.userid and money > t.money
or (money = t.money and year > t.year)
)

Sum values based on ctiteria in MySQL

I have the following data table from which I would like to sum the values of the field 'pts' for each 'pid' as follows:
The sum of the top 3 values per 'cont' plus the values of any other 'cont' per 'pid'. The results should be presented in DESC order by 'total'
+--------+-----+------+
| pid | pts | cont |
+--------+-----+------+
| 121693 | 40 | 1 |
| 121693 | 80 | 2 |
| 121693 | 120 | 1 |
| 121693 | 100 | 1 |
| 121693 | 500 | 1 |
| 121694 | 20 | 1 |
| 121694 | 0 | 2 |
| 121694 | 30 | 3 |
| 121695 | 0 | 1 |
| 121695 | 30 | 2 |
| 121695 | 0 | 1 |
+--------+-----+------+
In this example the query should return something like this
+--------+-------+
| pid | total |
+--------+-------+
| 121693 | 800 |
| 121694 | 50 |
| 121695 | 30 |
+--------+-------+
Is this possible?
Thanks in advance.
SELECT DISTINCT pid, SUM(Pts) AS Total
FROM your tablename
GROUP BY Pid
ORDER BY TOTAL
(Requires testing and minor fixes on small syntax)

MySQL select rows by max(column) by another column in SQL while using GROUP BY WEEK?

For each week I want to select the rows which have the highest weight for each distinct value of reps
-------------------------------------
| id | reps | weight | date |
| 1 | 1 | 15 | 2015-06-10 |
| 2 | 2 | 29 | 2015-06-12 |
| 3 | 1 | 30 | 2015-06-13 |
| 4 | 4 | 11 | 2015-06-14 |
| 5 | 1 | 15 | 2015-06-29 |
| 6 | 1 | 9 | 2015-06-30 |
and I would like it to return
-------------------------------------
| id | reps | weight | date |
| 2 | 2 | 29 | 2015-06-12 |
| 3 | 1 | 30 | 2015-06-13 |
| 4 | 4 | 11 | 2015-06-14 |
| 5 | 1 | 15 | 2015-06-29 |
I've tried
SELECT MAX(weight) as weight, reps, date, id FROM log_items
GROUP BY reps, WEEK(date)
But it seems to return completely random results, I have also tried using sub queries but they didn't work either (I'm guessing I was doing it wrong)
Try:
SELECT *
FROM log_items
WHERE (reps, WEEK(date), weight ) in (
SELECT reps, WEEK(date) , MAX(weight)
FROM log_items
GROUP BY reps, WEEK(date)
)
demo: http://sqlfiddle.com/#!9/4bf84/9

Calculate sum of Single column on the Basis of another column in MySQL

I want to get two(2) different SUM of Single Column "Amount" on the Basis of "Type" Column.
mysql>select * from tbl;
+--------+----------+----------+----------+
| id | CustID | amount | type |
+--------+----------+----------+----------+
| 1 | 1 | 100 | 0 |
| 2 | 2 | 200 | 0 |
| 3 | 3 | 200 | 0 |
| 4 | 1 | 100 | 1 |
| 5 | 1 | 100 | 0 |
| 4 | 3 | 100 | 0 |
| 5 | 1 | 300 | 1 |
| 6 | 2 | 100 | 1 |
+--------+----------+----------+----------+
mysql>Query Result (Want this Result);
+-------------+-------------+-------------+
| CustID | amount1 | amount2 |
+-------------+-------------+-------------+
| 1 | 200 | 400 |
| 2 | 200 | 100 |
| 3 | 400 | 0 |
+-------------+-------------+-------------+
Means to say that in above example there is Type column which only have 0 or 1 and I want to get sum of "Amount" column group by "CustID".
seems that is the query you are looking for.
SELECT
CustID,
SUM(IF(type=0,amount,0)) as amount1,
SUM(IF(type=1,amount,0)) as amount2
FROM
tbl
GROUP BY
CustID;
SELECT CustID,SUM(CASE WHEN `type` =0 THEN amount ELSE 0 END) AS amount1,
SUM(CASE WHEN `type` =1 THEN amount ELSE 0 END) AS amount2
FROM tableName GROUP BY CustID