Getting difference of a column from same table - mysql

I have a table with data that is similar to this table below
id
session_id
user_id
action
log_time
1
1
3
join
1642645048
2
1
3
left
1642645048
3
1
3
join
1642645552
4
1
3
left
1642646072
5
1
3
join
1642646632
6
1
3
left
1642646736
7
1
5
join
1642647083
8
1
5
join
1642649879
9
1
5
left
1642649951
10
1
5
join
1642650112
11
1
5
join
1642650159
12
1
5
join
1642651005
log_time is saved as a unix time
Question: Is it possible to retrieve the total amount of time that a user was in a session?
So it would do something like total_participation = ("1st left" - "1st join") + ("2nd left" - "2nd join") + ("3rd left - "3rd join")
I've already got the difference between first join and last left time by doing the following query:
SELECT s1.session_id as 'Id',
u.name AS 'Participant',
IFNULL(TIME_FORMAT(SEC_TO_TIME(s2.time_log - s1.time_log), '%Hh %im %ss'), 0) AS 'TotalParticipation'
FROM tblSessionLog AS s1
LEFT JOIN tblSessionLog AS s2 ON (
s2.id = (
SELECT id
FROM tblSessionLog
WHERE action = 'left'
AND user_id = s1.user_id
AND id > s1.id
ORDER BY time_log DESC
LIMIT 1
)
)
LEFT JOIN tblUser AS u ON u.id = s1.user_id -- used only to get participant name
WHERE s1.action = 'join'
GROUP BY s1.session_id, s1.user_id
ORDER BY s1.session_id, s1.user_id;
But I can't seem to get how to remove the time in between the participant have left and join backed again. Or is that not possible on SQL and should be handled on backend code?
Not exactly a copy but more or less, the actual data is like this sample SQL Fiddle here: http://sqlfiddle.com/#!9/2d8f6c/1/0
Update:
#Akina's solution work well when the data is consistent which is very much appreciated but I found out it will not be suitable on my case as there are instances where it is possible to have more than one join action before having left action, as well as no left action after join action. I updated the example table above to further show the actual data. Updated the sample fiddle as well.
Any leads will be really appreciated. And apologies as for some reason I need to do this on MySQL without the help of backend code. Thanks!

Perhaps something like this:
WITH cte1 AS(
SELECT user_id, action, time_log,
ROW_NUMBER() OVER (PARTITION BY user_id, action ORDER BY time_log) AS rn
FROM tblSessionLog
WHERE action='join'),
cte2 AS(
SELECT user_id, action, time_log,
ROW_NUMBER() OVER (PARTITION BY user_id, action ORDER BY time_log) AS rn
FROM tblSessionLog
WHERE action='left')
SELECT *,
cte2.time_log-cte1.time_log
FROM cte1
LEFT JOIN cte2
ON cte1.user_id=cte2.user_id
AND cte1.rn=cte2.rn;
Which on your current data will return the following results:
user_id
action
time_log
rn
user_id
action
time_log
rn
cte2.time_log-cte1.time_log
3
join
1642645048
1
3
left
1642645048
1
0
3
join
1642645552
2
3
left
1642646072
2
520
3
join
1642646632
3
3
left
1642646736
3
104
5
join
1642647083
1
5
left
1642649951
1
2868
5
join
1642649879
2
NULL
NULL
NULL
NULL
NULL
5
join
1642650112
3
NULL
NULL
NULL
NULL
NULL
5
join
1642650159
4
NULL
NULL
NULL
NULL
NULL
5
join
1642651005
5
NULL
NULL
NULL
NULL
NULL
With two generated common table expressions (cte), each result from cte assigned with ROW_NUMBER() then LEFT JOIN them with matching user_id and the generated ROW_NUMBER(). As your sample data only have 1 left for user_id=5 then it only paired for the first found join for the same user_id and returned NULL for the rest. If a left action was added for user_id=5 afterwards, it will occupy as the left action for the join action that is NULL. For example, if we add:
INSERT INTO tblSessionLog (id, session_id, user_id, action, time_log)
VALUES (13, 1, 5, 'left', 1642652005);
INSERT INTO tblSessionLog (id, session_id, user_id, action, time_log)
VALUES (14, 1, 5, 'left', 1642652085);
then the results will be something like this:
user_id
action
time_log
rn
user_id
action
time_log
rn
cte2.time_log-cte1.time_log
3
join
1642645048
1
3
left
1642645048
1
0
3
join
1642645552
2
3
left
1642646072
2
520
3
join
1642646632
3
3
left
1642646736
3
104
5
join
1642647083
1
5
left
1642649951
1
2868
5
join
1642649879
2
5
left
1642652005
2
2126
5
join
1642650112
3
5
left
1642652085
3
1973
5
join
1642650159
4
NULL
NULL
NULL
NULL
NULL
5
join
1642651005
5
NULL
NULL
NULL
NULL
NULL
One thing to take note is that the ROW_NUMBER() I generated here is order by time_log and not by id (or which data was inserted first). If you wish to pair them by which data comes first, you can simply change the ORDER BY time_log to ORDER BY id on both of the ROW_NUMBER() assignment.
Demo fiddle

Related

MySQL Select first entry of GROUP after custom ORDER BY

I'm trying to fetch the first entry of each group after the custom ORDER BY but don't know how to select that first entry of each group. The groups should be ordered by the #team if exist, otherwise other team else NULL.
SELECT t1.*
FROM tbl t1
INNER JOIN tbl2 t2 ON t2.group_id = t1.group
WHERE t2.region = #region
ORDER BY
CASE
WHEN team=#team THEN 1
WHEN team is NOT NULL THEN 2
WHEN team is NULL THEN 3
END
Content of tbl
id group team
1 1 AA
2 1 BB
3 2 AA
4 2 CC
5 3 BB
6 3 NULL
7 4 NULL
Expected result when #team=AA
id group team
1 1 AA
3 2 AA
5 3 BB
7 4 NULL
Expected result when #team=BB
id group team
2 1 BB
3 2 AA
5 3 BB
7 4 NULL
Use your custom ordering logic with ROW_NUMBER:
WITH cte AS (
SELECT t1.*, ROW_NUMBER() OVER (PARTITION BY t1.`group`
ORDER BY CASE WHEN team = #team THEN 1
WHEN team IS NOT NULL THEN 2
ELSE 3 END) rn
FROM tbl t1
INNER JOIN tbl2 t2 ON t2.group_id = t1.`group`
WHERE t2.region = #region
)
SELECT id, `group`, team
FROM cte
WHERE rn = 1;
Side note: Avoid naming your table columns GROUP, as this is a reserved MySQL keyword, and therefore must always be escaped in backticks.

Usage of COALESCE

I have a query making use of COALESCE() to count the combination of 2 columns:
SELECT method, main_ingredient, COUNT(*) AS cnt FROM `recipes`
GROUP BY COALESCE( method, main_ingredient )
The result is useful. Sample result:
method main_ingredient cnt
================================
1 4 10
2 1 6
3 6 3
4 6 5
5 2 4
6 8 2
However, how can I obtain the results that has COUNT(*) equals to 0 ?
UPDATE with expected output:
method main_ingredient cnt
================================
1 2 0
1 3 0
1 5 0
1 6 0
2 2 0
2 3 0
.
.
.
.
etc
UPDATE added the tbl_methods and tbl_main_ingredients:
Schema of tbl_methods:
id method_name
=================
1 Method 1
2 Method 2
.
.
.
6 Method 6
Schema of tbl_main_ingredients:
id ingredient_name
======================
1 Ingredient 1
2 Ingredient 2
.
.
.
8 Ingredient 8
Both id are the primary key of their table, auto-increment.
First you need to make a CROSS JOIN between tbl_methods and tbl_main_ingredients table in order to obtain the all possible combination of method and ingredient.
Later make a left join between the above cross joined table and your reipes table on matching method and main_ingredient.
Thus you will obtain a result for all possible combination of method and main_ingredient. If any combination exists in recipes table then you will get the corresponding count otherwise you will obtain 0 as count.
SELECT
method_ingredients.method_id,
method_ingredients.ingredients_id,
COUNT(R.method) AS cnt
FROM
(
SELECT
TM.id AS method_id,
TMI.id AS ingredients_id
FROM tbl_methods TM
CROSS JOIN tbl_main_ingredients TMI
) AS method_ingredients
LEFT JOIN `recipes` R ON R.method = method_ingredients.method_id AND R.main_ingredient = method_ingredients.ingredients_id
GROUP BY method_ingredients.method_id, method_ingredients.ingredients_id
ORDER BY method_ingredients.method_id, method_ingredients.ingredients_id;
Or
you can prefer the shorter version of this query:
SELECT
TM.id AS method_id,
TMI.id AS ingredients_id,
COUNT(R.method) AS cnt
FROM tbl_methods TM
CROSS JOIN tbl_main_ingredients TMI
LEFT JOIN `recipes` R ON R.method = TM.id AND R.main_ingredient = TMI.id
GROUP BY TM.id, TMI.id
ORDER BY TM.id, TMI.id;
More:
Some subtleties regarding COUNT:
SELECT COUNT(0); Result: 1
SELECT COUNT(-1); Result: 1
SELECT COUNT(NULL); Result: 0
SELECT COUNT(71); Result: 1
SQL FIDDLE
BTW there's nothing to do with COALESCE in your use case. COALESCE returns the first non-NULL element from the list if there's any otherwise NULL.
Example:
SELECT COALESCE(NULL,NULL,NULL,'abc',NULL,'def'); returns abc
SELECT COALESCE(NULL,NULL,NULL); returns NULL
Could be you need to check if the main_ingredient is null
SELECT method, ifnull(main_ingredient,0), COUNT(*) AS cnt FROM `recipes`
GROUP BY method
Cross join your 2 base tables, then left join on recipes. Then, if you count any of the left joined columns, you will get the desired result:
select m.id, i.id, count(r.method) as cnt
from tbl_methods m
cross join tbl_main_ingredients i
left join recipes r
on r.method = m.id
and r.main_ingredient = i.id
group by m.id, i.id
order by m.id, i.id

MySQL - Display null column from child table if all values are not distinct

I have the following tables, for example:
invoices
ID Name
1 A
2 B
3 C
4 D
5 E
transactions
ID Invoice_ID User_ID
1 1 10
2 1 10
3 1 10
4 2 30
5 3 20
6 3 40
7 2 30
8 2 30
9 4 40
10 3 50
Now I want to make a select that will pull the invoices and the user_id from the related transactions, but of course if I do that I won't get all the ids, since they may be distinct but there will be only one column for that. What I want to do is that if there are distinct User_ids, I will display a pre-defined text in the column instead of the actual result.
select invoices.id, invoices.name, transactions.user_id(if there are distinct user_ids -> return null)
from invoices
left join transactions on invoices.id = transactions.invoice_id
and then this would be the result
ID Name User_ID
1 A 10
2 B 30
3 C null
4 D 40
5 E null
Is this possible?
You can do the following :
select
invoices.id,
invoices.name,
IF (
(SELECT COUNT(DISTINCT user_id) FROM transactions WHERE transactions.invoice_id = invoices.id) = 1,
(SELECT MAX(user_id) FROM transactions WHERE transactions.invoice_id = invoices.id),
null
) AS user_id
from invoices
Or, alternatively, you can use the GROUP_CONCAT function to output a comma-separated list of users for each invoice. It is not exactly what you asked, but maybe in fact it will be more useful :
select
invoices.id,
invoices.name,
GROUP_CONCAT(DISTINCT transactions.user_id SEPARATOR ',') AS user_ids
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
Try somethingh like:
select i.id, i.name, t.user_id
from invoices i left join
(
select invoice_ID, User_ID
from transactions
group by invoice_ID
having count(invoice_ID)=1
) t on i.id=t.invoice_id
SQL fiddle
You could list all the transactions that have multiple user ids, like this:
select invoices.id, invoices.name, null
from invoices
left join transactions on invoices.id = transactions.invoice_id having count(distinct transactions.user_id) > 1
Also, I think this CASE might suit your needs here:
select invoices.id, invoices.name,
case when count(distinct transactions.user_id) > 1 then null else transactions.user_id end
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
although, I'm not sure this is syntactically correct

MySQL:Case Statement Issue

Complaint_status Table : multiple rows for a complaints
id complaint_id status_type status_value
1 11 2 0
2 11 2 2
3 11 2 1
4 11 2 2
Trying to get min(id) having status_type=2 and status_value=2 but showing null,have to use case statement for doing other stuff.
SELECT c3.id AS Ticket_id,
CASE
WHEN cs.status_value=2 THEN cs.created_at
ELSE NULL
END AS Closure_date
FROM complaint c3
INNER JOIN complaint_status cs ON cs.complaint_id=c3.id
WHERE cs.id IN
(SELECT min(id)
FROM complaint3_status
WHERE complaint_id=c3.id
AND status_type=2)
AND c3.id IN(11)
Here is one method that gives you the entire row:
select cs.*
from complaint_status cs
where status_type = 2 and status_value = 2
order by id asc
limit 1;
You can just use select id if that is all you want. I have no idea what case has to do with this query.

SQL - How to calculate column value and join with another table

As I am not good with MySQL query's so I wish someone help me for creating this kind of sql query.
I having two MySQL tables which is describe bellow:
Table Name: rating
-------------------
property_id user_id area_rate_count safety_rate_count friendly_rate_count walkability_rate_count
4 28 1 1 1 2
5 38 2 3 4 1
5 40 2 2 3 1
6 40 2 3 1 4
10 43 2 2 3 1
Table Name: listing
-------------------
property_id title
4 Sample 1
5 Sample 2
6 Sample 3
10 Sample 4
11 Sample 5
12 Sample 6
Now first I want to sum each column and divide. (area_rate_count, safety_rate_count, friendly_rate_count, walkability_rate_count). For example In property_id:5 having two times so first calculate column sum and divide by 2.
After calculation we will get this output:
Table Name: rating (After Calculation)
--------------------------------------
property_id rate
4 5
5 9 (Divided by 2 because this property_id is two times in table)
6 10
10 8
And Finally I want join this result to my listing table and result looks something like this:
Table Name: listing
-------------------
property_id title rate
4 Sample 1 5
5 Sample 2 9 (Divided by 2 becouse property_id is two times in table)
6 Sample 3 10
10 Sample 4 8
11 Sample 5 0
12 Sample 6 0
Thanks.
I think you want the avg() aggregation function along with a join:
select l.property_id, l.title,
coalesce(avg(area_rate_count + safety_rate_count + friendly_rate_count + walkability_rate_count
), 0) as rate
from listing l left outer join
property_id p
on l.property_id = p.property_id
group by l.property_id, l.title ;
If I understood it right I think you need this:
select l.property_id, l.title, coalesce(r.ssum/if(r.ct=0,1,r.ct), 0) as rate
from listing l LEFT JOIN
(select property_id,
sum(area_rate_count+safety_rate_count
+friendly_rate_count+walkability_rate_count) ssum,
count(*) ct
from rating
group by property_id ) r
ON l.property_id = r.property_id
order by l.property_id
See it here on fiddle: http://sqlfiddle.com/#!2/589d6/5
Edit
As OP asked on the comments that he wants all columns from listing here is what he want:
select l.*, coalesce(r.ssum/if(r.ct=0,1,r.ct), 0) as rate
from listing l LEFT JOIN
(select property_id,
sum(area_rate_count+safety_rate_count
+friendly_rate_count+walkability_rate_count) ssum,
count(*) ct
from rating
group by property_id ) r
ON l.property_id = r.property_id
order by l.property_id
CREATE TEMPORARY TABLE IF NOT EXISTS
temp_table ( INDEX(col_2) )
ENGINE=MyISAM
AS (
SELECT
property_id,
AVG(area_rate_count) as area_rate_count,
AVG(safety_rate_count) as safety_rate_count,
AVG(friendly_rate_count) as friendly_rate_count,
AVG(walkability_rate_count) as walkability_rate_count
FROM rating
GROUP BY property_id
)
SELECT * FROM listing L
JOIN temp_table T
ON L.property_id = T.property_id
Use the below statement to get distinct property_id with its own rate
select property_id, sum(separaterating)/count(property_id) from (
select property_id,sum(area_rate_count , safety_rate_count , friendly_rate_count , walkability_rate_count) as separaterating from rating group by property_id AS temp ) group by
property_id
you can then join with the other table to get the final result as below
select * from ( select property_id, sum(separaterating)/count(property_id) from (
select property_id,sum(area_rate_count , safety_rate_count , friendly_rate_count , walkability_rate_count) as separaterating from rating group by property_id AS temp ) group by
property_id) AS A inner join listing AS B on A.property_id = B.property_id
try this:
select a.prop_id as property_id, l.title, a.allratings / b.numberofreviews as rate
from
(
select property_id as prop_id, SUM(coalesce(area_rate_count,0) + coalesce(safety_rate_count,0) + coalesce(friendly_rate_count,0) + coalesce(walkability_rate_count,0)) as allratings
from rating
group by property_id
) a inner join
(
select property_id, count(distinct user_id) as numberofreviews
from rating
group by property_id
) b on a.property_id = b.property_id
inner join listing l on a.property_id = l.property_id
Try This Query
select ls.property_id,ls.title,inr.rate from listing as ls
left join
(select r.property_id as pid,r.rate/r.cnt as rate from
(select property_id,user_id,(area_rate_count+safefty_rate_count+friendly_rate_count+walkability_rate_count) as rate,count(*) as cnt from rating group by property_id) as r) as inr on inr.pid=ls.property_id