How to count values within a range in my MySQL table - mysql

Hello i im trying to count the values within each range e.g. between 115000 - 120000 in my DURATION_IN_MS column.
my column looks like this:
119631
120689
143498
119798
WITH tab1 AS(
SELECT TOP 30 *
FROM MACHINE_PROCESSING_DURATION_EVALUATION
WHERE START_TIMESTAMP BETWEEN '2022-11.10 00:00:00.000' AND '2022-11.10 22:00:00.000')
SELECT(
case
when DURATION_IN_MS BETWEEN 115000 AND 120000 THEN '115000-120000'
when DURATION_IN_MS BETWEEN 120000 AND 125000 THEN '120000-125000'
else 'OTHERS'
END) AS DURATION_IN_MS,
COUNT(*) AS cnt
from tab1
GROUP BY DURATION_IN_MS
my output is like this:
[enter image description here][1]
however, for the range 115000-120000 i wanted to show the count of 8
for the range 120000-125000 i wanted to show the count of 6
Can someone help me ?

ref: fiddle
SELECT
case
when DURATION_IN_MS BETWEEN 115000 AND 120000 THEN '115000-120000'
when DURATION_IN_MS BETWEEN 120001 AND 125000 THEN '120001-125000'
else 'OTHERS'
END AS grp,
COUNT(*) AS cnt
from tab1
GROUP BY grp
Like ysth said in comments, separate the ranges.
The case statement defines the categories as grp, we GROUP BY this and the amount count(*) is per group.

See if you like this technique:
SELECT
FLOOR(DURATION_IN_MS / 5000) * 5000 AS 'bucket start',
FLOOR(DURATION_IN_MS / 5000 + 1) * 5000 - 1 AS 'bucket end',
COUNT(*) AS cnt
FROM MACHINE_PROCESSING_DURATION_EVALUATION
WHERE ...
GROUP BY 1, 2
ORDER BY cnt DESC
LIMIT 30;
It does not match your question exactly but is more flexible in because it automatically creates all the necessary ranges. (Not WITH is needed.)
To get an "other", I might UNION with another query that inverts it. (This is messier.)

Related

SQL Query Sequential Month Logins

I have the following SQL table
username
Month
292
10
123
12
123
1
123
2
123
4
345
6
345
7
I want to query it, to get each username's login streak in Count of sequential Month. meaning the end result I am looking for looks like this :
username
Streak
292
1
123
3
345
2
How can I achieve it ? taking into note the Month 12 --> Month 1 issue;
Appreciate your help;
This would give you the result you want:
select username, count(*)
from (
select
username
, month_1
, coalesce(nullif(lead(month_1)
over (partition by username
order by coalesce(nullif(month_1,12),0))
- coalesce(nullif(month_1,12),0),-1),1) as MonthsTillNext
from login_tab
) Step1
where MonthsTillNext=1
group by username
By calculating the difference from the next row, where the next row is defined as the next month_no in ascending order, treating 12 as 0 (refer to the ambiguity I mentioned in my comment). It then just leaves the rows for consecutive months rows, and counts them.
Beware though, in addition to the anomaly around month:12, there is another case not considered: if the months for the user are 1,2,3 and 6,7,8 this would count as Streak:6; is it what you wanted?
One way would be with a recursive CTE, like
WITH RECURSIVE cte (username, month, cnt) AS
(
SELECT username, month, 1
FROM test
UNION ALL
SELECT test.username, test.month, cte.cnt+1
FROM cte INNER JOIN test
ON cte.username = test.username AND CASE WHEN cte.month = 12 THEN 1 ELSE cte.month + 1 END = test.month
)
SELECT username, MAX(cnt)
FROM cte
GROUP BY username
ORDER BY username
The idea is that the CTE (named cte in my example) recursively joins back to the table on a condition where the user is the same and the month is the next one. So for user 345, you have:
Username
Month
Cnt
345
6
1
345
7
1
345
7
2
The rows with cnt=1 are from the original table (with the extra cnt column hardcoded to 1), the row with cnt=2 is from the recursive part of the query (which found a match and used cnt+1 for its cnt). The query then selects the maximum for each user.
The join uses a CASE statement to handle 12 being followed by 1.
You can see it working with your sample data in this fiddle.
The one shared by #EdmCoff is quite elegant.
Another one without recursive and just using conditional logic -
with data_cte as
(
select username, month_1,
case when (count(month_1) over (partition by username) = 1) then 1
when (lead(month_1) over (partition by username order by username) - month_1) = 1 OR (month_1 - lag(month_1) over (partition by username order by username)) = 1 then 1
when (month_1 = 12 and min (month_1) over (partition by username) =1) then 1
end cnt
from login_tab
)
select username, count(cnt) from data_cte group by username
DB Fiddle here.

MySQL Return latest and longest streak of rows with certain conditions

Please have a look at this fiddle.
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/7
I'm trying to accomplish 2 things:
How do I get the length and date of the LATEST STREAK OF CORRECT GUESSES (meaning Result = Guess) without any skipped dates? In this case, it would be 4, starting from 2021-01-05 to 2021-01-08. (Although 2021-01-03 is correct, because there was no guess on 2021-01-04, it should not be included).
How do I get the length and date of the LONGEST STREAK OF CORRECT GUESSES OF ALL TIME? Again meaning Result = Guess, but can be anywhere in the table. Let's say it's 10 from 3 months ago.
To further complicate things, guesses can be made by multiple users AND there will be multiple results (for different game categories for example) on the same day. So the table above is for one user and one game category. I think I can handle this if I can get some guidance on the goals up above.
This is beyond my understanding. Any and all help would be appreciated.
EDIT: I've changed the table to show that the date is not always sequential. Also, I was informed that I should be using MySQL 8.0 for this task as using variables is not good practice for this problem.
Edit: Using the window functions, starting to get somewhere:
Please check the fiddle. It's pretty close to what I'm trying to get to, but the '4' in the total column should be a 1. In other words, the "sum" should restart. Not sure how to achieve this, because it's clear that the window function will group based on the conditions, breaking the order and thus the streak.
Updated: I've updated the fiddle per #The Impaler's request. The table here is more representative to what I'm actually working with (still not exact, but much closer).
Since this new fiddle is more representative, I'll also explain my final goal. I'd also like to get the streak for each game_type. The way I've been comparing game_type result on a given day to "community" (basically all the users) guess is by summing all the 0's and 1's for each game_type on that date from all the users and then using whichever greater as the 'guess'. This way, I can get how the "community" is doing as a whole. This works for individual dates, but to do a streak, I'm not sure.
Update 2
So this is as far as I've got:
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/11
I tried to do a nested window function but that's not allowed. I have the proper groupings and column for when guess = result. Now I need help figuring out the streak within the groups.
This is a typical "Gaps & Islands" problem. Once you assemble the islands the query becomes easy.
For example, for a single user, as stated in the fiddle you can get the LONGEST STREAK by doing:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by streak_length desc
limit 1;
Result:
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
To get the LATEST STREAK you just need to change the ORDER BY clause at the end as shown below:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by ending_day desc
limit 1;
Result (same result as before):
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
See running example at DB Fiddle.
Note: You can remove the LIMIT clause at the end to see all the islands, not just the selected one.
For multi-users it's just a matter of modifying the windows (adding partitioning) and the rest of the query remains the same. If you provide a fiddle for multi-users I can add the solution as well.
So, it took a while, but thanks to #The Impaler providing me the basis and the link below, I was able to solve the problem.
https://www.red-gate.com/simple-talk/sql/t-sql-programming/efficient-solutions-to-gaps-and-islands-challenges/
Here is the full solution:
with GAME_LOG as (
select
*,
guess = result as correct,
lag(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as previous_game_result,
lead(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as next_game_result,
row_number() over(partition by user_id, game_type order by dayt DESC) as ilocation
from mytable
),
CTE_ISLAND_START as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_start_time,
ilocation as island_start_location
from GAME_LOG
where correct = 1 AND
(previous_game_result <> 1 OR previous_game_result is null)
),
CTE_ISLAND_END as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_end_time,
ilocation as island_end_location
from GAME_LOG
where correct = 1 AND
(next_game_result <> 1 OR next_game_result is null)
)
select
CTE_ISLAND_START.user_id,
CTE_ISLAND_START.game_type,
CTE_ISLAND_START.island_start_time as streak_end,
CTE_ISLAND_END.island_end_time as streak_start,
cast(CTE_ISLAND_END.island_end_location as signed) -
cast(CTE_ISLAND_START.island_start_location as signed) + 1 as streak
from CTE_ISLAND_START
inner join CTE_ISLAND_END
on CTE_ISLAND_START.inumber = CTE_ISLAND_END.inumber AND
CTE_ISLAND_START.user_id = CTE_ISLAND_END.user_id AND
CTE_ISLAND_START.game_type = CTE_ISLAND_END.game_type
This will give all the streaks for each user_id, each game_type, as well as the start and end dates of the streak.
You can simply add a WHERE clause to filter by game_type and user_id.
Here's the fiddle with slightly updated dataset.
Fiddle

How to sum and multiply specific values by a number in MYSQL?

I have a table containing list of actions performed by persons.
After
SELECT COUNT(NICK) AS TOTAL, NICK, ACTION FROM ACTIONS2 GROUP BY ACTION,NICK ORDER BY NICK ASC, TOTAL DESC
it looks like this:
total nick action
13 0xlne destroyed
5 0xlne captured
5 0xlne deployed
1 13key destroyed
2 74pawel deployed
1 74pawel destroyed
1 74pawel captured
Now, I would like to multiply action destroy x75,capture x500, deploy x125 and so on, so first row should look: (13*75+5*500+5*125=4100)
total nick
4100 0xlne
75 13key
and so on. Is there a way to correlate this few types of actions with values without creating new table, and joining them before multiply and sum?
It's a matter of using CASE/WHEN
SELECT
SUM(CASE WHEN action='destroyed' THEN total*75 WHEN action='captured' THEN total*500 ELSE total*125 END), nick
FROM (SELECT COUNT(NICK) AS TOTAL, NICK, ACTION FROM ACTIONS2 GROUP BY ACTION,NICK ORDER BY NICK ASC, TOTAL DESC) AS nicks
GROUP by nick;
As a footnote: generally upper case is used for SQL reserved words and lower case for column/table names.
Use conditional aggregation and sum the weighted values:
SELECT NICK, COUNT(*) AS totalAction
COALESCE(SUM(ACTION='destroyed'),0)*75 +
COALESCE(SUM(ACTION='capture'),0)*500 +
COALESCE(SUM(ACTION='deploy'),0)*125
as totalPoints
FROM ACTIONS2
GROUP BY NICK
ORDER BY NICK ASC, totalAction DESC
The expression
COALESCE(SUM(ACTION='destroyed'),0)*75
can aslo be written as
COUNT(CASE WHEN ACTION='destroyed' THEN 1 END)*75
Or
COUNT(ACTION='destroyed' OR NULL)*75
Or many other ways.
A little bit different approach:
SELECT NICK, COUNT(*) AS totalAction,
SUM(
CASE ACTION
WHEN 'destroyed' THEN 75
WHEN 'capture' THEN 500
WHEN 'deploy' THEN 125
ELSE 0
END
) as totalPoints
FROM ACTIONS2
GROUP BY NICK
ORDER BY NICK ASC, totalAction DESC

mysql : display multiple results with heading

I'm a complete newb so forgive me.
I'm trying to get the results to display 2 or more different headings.
SELECT sum(fare) AS totalfare, count(*) AS fare10
where fare>10
FROM tbl
I'm trying to get the WHERE statement apply to only count, not the sum, and have the result display as "totalfare" "fare10"
SELECT sum(fare) AS totalfare
FROM tbl
union
SELECT count(*) AS watever
FROM tbl
where fare > 10
I've tried this way, but the result grid would spit out the answers under 1 heading as totalfare. Is it possible to display it as totalfare | whatever?
Finally you explained your question. You can do UNION only when you have tables (result sets) with the same fields. This is what you need. The above query selects directly from the derived table created by the two sub-queries.
SELECT
*
FROM
(SELECT
SUM(fare) AS totalfare
FROM
tbl) a,
(SELECT
COUNT(*) AS watever
FROM
tbl
WHERE
fare > 10) b
You will get results as one row
[ totalfare | watever ]
number number
You want conditional aggregation:
SELECT sum(fare) AS totalfare,
sum(case when fare > 10 then 1 else 0 end) as fare10
FROM tbl;
In MySQL you can also shorten this to:
SELECT sum(fare) AS totalfare,
sum(fare > 10) as fare10
FROM tbl;

SELECT rows with minimum count(*)

Let's say i have a simple table voting with columns
id(primaryKey),token(int),candidate(int),rank(int).
I want to extract all rows having specific rank,grouped by candidate and most importantly only with minimum count(*).
So far i have reached
SELECT candidate, count( * ) AS count
FROM voting
WHERE rank =1
AND candidate <200
GROUP BY candidate
HAVING count = min( count )
But,it is returning empty set.If i replace min(count) with actual minimum value it works properly.
I have also tried
SELECT candidate,min(count)
FROM (SELECT candidate,count(*) AS count
FROM voting
where rank = 1
AND candidate < 200
group by candidate
order by count(*)
) AS temp
But this resulted in only 1 row,I have 3 rows with same min count but with different candidates.I want all these 3 rows.
Can anyone help me.The no.of rows with same minimum count(*) value will also help.
Sample is quite a big,so i am showing some dummy values
1 $sampleToken1 101 1
2 $sampleToken2 102 1
3 $sampleToken3 103 1
4 $sampleToken4 102 1
Here ,when grouped according to candidate there are 3 rows combining with count( * ) results
candidate count( * )
101 1
103 1
102 2
I want the top 2 rows to be showed i.e with count(*) = 1 or whatever is the minimum
Try to use this script as pattern -
-- find minimum count
SELECT MIN(cnt) INTO #min FROM (SELECT COUNT(*) cnt FROM voting GROUP BY candidate) t;
-- show records with minimum count
SELECT * FROM voting t1
JOIN (SELECT id FROM voting GROUP BY candidate HAVING COUNT(*) = #min) t2
ON t1.candidate = t2.candidate;
Remove your HAVING keyword completely, it is not correctly written.
and add SUB SELECT into the where clause to fit that criteria.
(ie. select cand, count(*) as count from voting where rank = 1 and count = (select ..... )
The HAVING keyword can not use the MIN function in the way you are trying. Replace the MIN function with an absolute value such as HAVING count > 10