mysql sum, CASE WHEN with GROUP BY - mysql

In MySQL, I want to sum the values ​​of certain statuses and display them in the same count column,
Do I have to give a condition to count ?!
status is 0, 1, 2 and 3, and status = 2 is a sum of 2, 3, 4 count values.
What kind of conditions should I give?
My Query:
SELECT A.STATUS, B.COUNT, B.REG_DT FROM
(SELECT 0 AS STATUS UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) A
LEFT JOIN (
SELECT STATUS , COUNT(*) AS COUNT, FROM TB_EXE WHERE reg_dt >= CURDATE()
GROUP BY STATUS
) B ON A.STATUS = B.STATUS
My Data:
status | count
-----------------
0 | 1
-----------------
1 | 2
-----------------
2 | 1
-----------------
3 | 0
-----------------
4 | 2
Expected Results:
status | count
-----------------
0 | 1
-----------------
1 | 2
-----------------
2 | 3

SELECT STATUS,COUNT(*)
FROM T
WHERE STATUS < 2
GROUP BY STATUS
UNION ALL
(SELECT 2,COUNT(*)
FROM T
WHERE STATUS >= 2
)
Where the 2 aggregations are dealt with separately.
+--------+----------+
| STATUS | COUNT(*) |
+--------+----------+
| 0 | 1 |
| 1 | 1 |
| 2 | 3 |
+--------+----------+
3 rows in set (0.00 sec)
Or more succinctly
select case when status > 1 then 2 else status end, count(*)
from t
group by case when status > 1 then 2 else status end

you can try like below using case when
select case when status>1 then 2 else status end as status,
sum(count) as cnt from t
group by status

Hmmm, I think I'd go with a framing solution on this one. IE something like this:
SELECT
Status,
Count,
SUM(Count) OVER(ORDER BY status ROWS
BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) AS FramedCount
FROM Status
this gives you a running total of the counts from all previous rows as the framed count. You can handle the logic in the application to determine which statuses should use the framed count OR you could handle it in the query by adding the following.
SELECT
status,
CASE
when status <= 3 THEN count
ELSE SUM(count) OVER(ORDER BY status ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
END AS 'MyCount'
FROM statusInfo

Related

Dividing new created columns

orders_table:
orders_id_column | user_id_column | final_status_column
----------------------------------------------------
1 | 4455 | DeliveredStatus
2 | 4455 | DeliveredStatus
3 | 4455 | CanceledStatus
4 | 8888 | CanceledStatus
I want to calculate the total number of orders, and the number of Canceled orders by user_id, and then the cocient between these two, to arrive to something like is:
user_id | total_orders | canceled_orders | cocient
---------------------------------------------------
4455 | 3 | 1 | 0.33
8888 | 1 | 1 | 1.00
I managed to create the first two columns, but not the last one:
SELECT
COUNT(order_id) AS total_orders,
SUM(if(orders.final_status = 'DeliveredStatus', 1, 0)) AS canceled_orders
FROM users
GROUP BY user_id;
You can use an easy approach :
SELECT
user_id,
COUNT(order_id) AS total_orders,
SUM(CASE WHEN final_status = 'CanceledStatus' THEN 1 ELSE 0 END ) AS
canceled_orders,
SUM(CASE WHEN final_status = 'CanceledStatus' THEN 1 ELSE 0 END ) /COUNT(order_id)
as cocient
FROM users
GROUP BY user_id;
Demo: https://www.db-fiddle.com/f/7yUJcuMJPncBBnrExKbzYz/136
You could just use a sub-query.
Then you can refer to the newly created columns, as the outer query exists in a different scope (one where the new columns now exist).
(Thus avoids repeating any logic, and maintaining DRY code.)
SELECT
user_id,
total_orders,
cancelled_orders,
cancelled_orders / total_orders
FROM
(
SELECT
user_id,
COUNT(order_id) AS total_orders,
SUM(if(orders.final_status = 'DeliveredStatus', 1, 0)) AS canceled_orders
FROM
users
GROUP BY
user_id
)
AS per_user
Note, selecting from the users table appears to be a typo in your example. It would appear that you should select from the orders table...

Limit query results based on value of multiple columns

I am using MySQL 5.6 and I have a table structure like below
| user_id | email_1 | email_2 | email_3 |
| 1 | abc#test.com | | |
| 2 | xyz#test.com | | joe#test.com |
| 3 | | test#test.com | bob#joh.com |
| 4 | | | x#y.com |
I want to fetch the first n email addresses from this table.
For example, if I want to fetch the first 5 then only the first 3 rows should return.
This makes certain assumptions about the uniqueness of data, that might not be true...
SELECT DISTINCT x.* FROM my_table x
JOIN
(SELECT user_id, 1 email_id,email_1 email FROM my_table WHERE email_1 IS NOT NULL
UNION ALL
SELECT user_id, 2 email_id,email_2 email FROM my_table WHERE email_2 IS NOT NULL
UNION ALL
SELECT user_id, 3 email_id,email_3 email FROM my_table WHERE email_3 IS NOT NULL
ORDER BY user_id, email_id LIMIT 5
) y
ON y.user_id = x.user_id
AND CASE WHEN y.email_id = 1 THEN y.email = x.email_1
WHEN y.email_id = 2 THEN y.email = x.email_2
WHEN y.email_id = 3 THEN y.email = x.email_3
END;
You want to return as many rows as necessary to get five emails. So you need a running total of the email count.
select user_id, email_1, email_2, email_3
from
(
select
user_id, email_1, email_2, email_3,
coalesce(
sum((email_1 is not null) + (email_2 is not null) + (email_3 is not null))
over (order by user_id rows between unbounded preceding and 1 preceding)
, 0) as cnt_prev
from mytable
) counted
where cnt_prev < 5 -- take the row if the previous row has not reached the count of 5
order by user_id;
You need a current MySQL version for SUM OVER to work.
The counting of the emails uses a MySQL feature: true equals 1 and false equals 0 in MySQL. Thus (email_1 is not null) + (email_2 is not null) + (email_3 is not null) counts the emails in the row.
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ac415e71733699547196ae01cb1caf13

Calculate unique items seen by users via sql

I need help to resolve the next case.
The data which users want to see is accessible by pagination requests and later these requests are stored in the database in the next form:
+----+---------+-------+--------+
| id | user id | first | amount |
+----+---------+-------+--------+
| 1 | 1 | 0 | 5 |
| 2 | 1 | 10 | 10 |
| 3 | 1 | 10 | 5 |
| 4 | 1 | 15 | 10 |
| 5 | 2 | 0 | 10 |
| 6 | 2 | 0 | 5 |
| 7 | 2 | 10 | 5 |
+----+---------+-------+--------+
The table is ordered by user id asc, first asc, amount desc.
The task is to write the SQL statement which calculate what total unique amount of data the user has seen.
For the first user total amount must be 20, since the request with id=1 returned first 5 items, with id=2 returned another 10 items. Request with id=3 returns data already 'seen' by request with id=2. Request with id=4 intersects with id=2, but still returns 5 'unseen' pieces of data.
For the second user total amount must be 15.
As a result of SQL statement, I should get the next output:
+---------+-------+
| user id | total |
+---------+-------+
| 1 | 20 |
+---------+-------+
| 2 | 15 |
+---------+-------+
I am using MySQL 5.7, so window functions are not available for me. I stuck with this task for a day already and still cannot get the desired output. If it is not possible with this setup, I will end up calculating the results in the application code. I would appreciate any suggestions or help with resolving this task, thank you!
This is a type of gaps and islands problem. In this case, use a cumulative max to determine if one request intersects with a previous request. If not, that is the beginning of an "island" of adjacent requests. A cumulative sum of the beginnings assigns an "island", then an aggregation counts each island.
So, the islands look like this:
select userid, min(first), max(first + amount) as last
from (select t.*,
sum(case when prev_last >= first then 0 else 1 end) over
(partition by userid order by first) as grp
from (select t.*,
max(first + amount) over (partition by userid order by first range between unbounded preceding and 1 preceding) as prev_last
from t
) t
) t
group by userid, grp;
You then want this summed by userid, so that is one more level of aggregation:
with islands as (
select userid, min(first) as first, max(first + amount) as last
from (select t.*,
sum(case when prev_last >= first then 0 else 1 end) over
(partition by userid order by first) as grp
from (select t.*,
max(first + amount) over (partition by userid order by first range between unbounded preceding and 1 preceding) as prev_last
from t
) t
) t
group by userid, grp
)
select userid, sum(last - first) as total
from islands
group by userid;
Here is a db<>fiddle.
This logic is similar to Gordon's, but runs on older releases of MySQL, too.
select userid
-- overall length minus gaps
,max(maxlast)-min(minfirst) + sum(gaplen) as total
from
(
select userid
,prevlast
,min(first) as minfirst -- first of group
,max(last) as maxlast -- last of group
-- if there was a gap, calculate length of gap
,min(case when prevlast < first then prevlast - first else 0 end) as gaplen
from
(
select t.*
,first + amount as last -- last value in range
,( -- maximum end of all previous rows
select max(first + amount)
from t as t2
where t2.userid = t.userid
and t2.first < t.first
) as prevlast
from t
) as dt
group by userid, prevlast
) as dt
group by userid
order by userid
See fiddle

Singular condition on an aggregate column

Slightly tough to define what I'm trying to get at, but taking a stab here. I'm working on redshift and writing a query on top of the following sample Table A:
User ID || Active_in_Month || Max_Months_On_Platform
1 1 6
1 2 6
1 5 6
2 1 3
2 3 3
After grouping by "Active_in_Month", I want to get at the following output in Table B:
Active_in_Month || Active_Distinct_Users || User_Cohorts
1 2 2
2 1 2
3 1 2
5 1 1
The "Active_Distinct_Users" is a simple COUNT(*). However, the calculation of "User_Cohorts" is where I'm stuck. The column is supposed to represent how many users were on the platform who have been active for at most the value in the "active_in_month" comlumn. For example, in Row 1 of Table B, there were two users who have "Max_Months_on_Platform" > 1 (Active in Month). In Row 5 of Table B there is only 1 "User_Cohort", because only 1 user has "Max Months on Platform" > 5 (Active_in_Month).
Hope this explains what I'm trying to get at.
Solution
Solved it using the following way, not sure if its the best way but it got the job done:
SELECT
Active_in_Month,
COUNT(DISTINCT user_id),
( SELECT
SUM(number_of_customers)
FROM (SELECT
tbl_a2.Max_Months_On_Platform AS total,
COUNT(DISTINCT tbl_a2.user_id) AS number_of_customers
FROM
tbl_a AS tbl_a2
GROUP BY tbl_a2.Max_Months_On_Platform
)
WHERE total + 1 >= tbl_a.Active_in_Month
) AS total_customers
FROM
tbl_a
I hope i have understand the correct rule to calculate the value of User_Cohorts. try this please:
SELECT
a.Active_in_Month
, COUNT(*) AS Active_Distinct_Users
, ( SELECT COUNT(DISTINCT user_id) +1
FROM tablea a2
WHERE a.Active_in_Month < a2.Max_Months_On_Platform
AND a.user_id <> a2.user_id
) AS User_Cohorts
FROM tablea a
GROUP BY a.Active_in_Month
ORDER BY a.Active_in_Month;
sample
MariaDB [test]> SELECT
-> a.Active_in_Month
-> , COUNT(*) AS Active_Distinct_Users
-> , ( SELECT COUNT(DISTINCT user_id) +1
-> FROM tablea a2
-> WHERE a.Active_in_Month < a2.Max_Months_On_Platform
-> AND a.user_id <> a2.user_id
-> ) AS User_Cohorts
-> FROM tablea a
-> GROUP BY a.Active_in_Month
-> ORDER BY a.Active_in_Month;
+-----------------+-----------------------+--------------+
| Active_in_Month | Active_Distinct_Users | User_Cohorts |
+-----------------+-----------------------+--------------+
| 1 | 2 | 2 |
| 2 | 1 | 2 |
| 3 | 1 | 2 |
| 5 | 1 | 1 |
+-----------------+-----------------------+--------------+
4 rows in set (0.00 sec)
MariaDB [test]>

Mysql query for counting winnings and losings

I have a table with some columns. One of the column registers if the row is true or false. E.g if you lost the game, this is set to 0 else 1.
What I want to count is the amount of 1 in row. The table looks like this:
+-----------+
|game_status|
|-----------|
|00000000 |
|-----------|
|00000000 |
|-----------|
|00000001 |
|-----------|
|00000001 |
|-----------|
|00000001 |
|-----------|
| ... |
+-----------+
So if you count it by hand the result would be:
starting from 0:
lost(0) -> 0 - 1 =
lost(0) -> -1 - 1 =
won(1) -> -2 + 1 =
won(1) -> -1 + 1 =
won(1) -> 0 + 1 =
Result = 1
So how do I get this result using mysql queries? I have tried using count but it counts all ones or zeros.
Thanks in advance.
I think I got a little bit confused when I accepted the answer and I do apologize for that. What I think I forgot to say is the result of the query should look like this:
+--------+------+
| 1 | -1 |
|--------|------|
| 2 | -2 |
|--------|------|
| 3 | -1 |
+---------------+
There the incremental column is the amount of games played...
You could count the wins and subtract the count of the loses.
select (select count(status) from table1 where status = 1) -
(select count(status) from table1 where status = 0)
try:
SELECT COUNT(DISTINCT CASE WHEN Column = 0 THEN 1 END) AS TotalZeros,
COUNT(DISTINCT CASE WHEN Column = 1 THEN 1 END) AS TotalOnes
FROM YourTable
Or much simpler way is if your column only holds either 0 or 1 just select count of either one of them and subtract it from the total rows which will give you the count of other.
Use SUM instead of COUNT. COUNT finds the number of items regardless of their value, SUM actually totals up the integer values.
Look here for reference
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
SELECT sum(CASE game_status WHEN 1 THEN 1 ELSE -1 end) FROM table_name;