Question about Self Joins. Seeing if two users interacted - mysql

I was given a SQL assignment that is the following table simulating a dating app: Table name = dating
user_id | viewing_profile_id | date | liked
1 2 x yes
1 3 x yes
2 1 x yes
2 3 x no
3 1 x no
3 2 x no
essentially I want to see the % of profiles that match each other. a match is when both profiles like each other.
I think I know what I have to do. Perform a Self join onto the table itself and use a case when for when the profiles like each other.
select t.date, sum(t.match) / count(t.match) as p_match
from (
select s1.user_id, s1.viewing_profile_id,
date_trunc('day',s1.date) as date, case when s1.liked = 'yes'
and s2.liked = 'yes' then 1 else 0 end as match
from dating s1
left join dating s2 on s2.ser_id = s1.viewing_profile_id
group by 1,2) t
group by 1
Since this is just a made up table, and I do not have any data execute this on, I was wondering if I can get some insight into whether or not this would work.
I am expecting the subquery to produce something like the following:
user_id | viewing_profile_id | date | match
1 2 x 1
1 3 x 0
2 1 x 1
2 3 x 0
3 1 x 0
3 2 x 0

You need to left self join the table on the conditions you have:
select
d1.*,
case when d2.user_id is null then 0 else 1 end `match`
from dating d1 left join dating d2
on
d1.user_id = d2.viewing_profile_id
and d1.viewing_profile_id = d2.user_id
and d1.liked = 'yes' and d2.liked = 'yes'
order by d1.user_id, d1.viewing_profile_id
See the demo.
Results:
| user_id | viewing_profile_id | date | liked | match |
| ------- | ------------------ | ---- | ----- | ----- |
| 1 | 2 | | yes | 1 |
| 1 | 3 | | yes | 0 |
| 2 | 1 | | yes | 1 |
| 2 | 3 | | no | 0 |
| 3 | 1 | | no | 0 |
| 3 | 2 | | no | 0 |

/* for matched users*/
SELECT *
FROM user viewed, user viewing
WHERE viewed.user_id = viewing.viewing_profile_id
and viewed.liked='yes'
and viewing.liked='yes'
/* for Match_Percentage */
SELECT
(SELECT count(*)
FROM user viewed, user viewing
WHERE viewed.user_id = viewing.viewing_profile_id
and viewed.liked='yes'
and viewing.liked='yes')
/
(SELECT count(*) FROM user)
*100 as Match_Percentage
FROM dual;

If you just want a flag specifying whether the reverse matches, then:
select d.*,
(exists (select 1
from dating d2
where d2.user_id = d.viewing_profile_id and
d2.viewing_profile_id = d.user_id and
d2.liked = 'yes'
) and
d.liked = 'yes'
) as match
from dating d;

Related

Select records in one table and specific matching records from the other

I have three tables, two of which are relevant for this question. Users, Things, User_to_thing
Users
ID | Name | Active
-------------------
1 | Joe | 1
2 | Jack | 1
3 | Tom | 1
4 | Harry | 0
5 | Stan | 1
6 | Bob | 1
User_to_thing
Thing ID | User ID | Status
---------------------------
3 | 1 | 1
3 | 2 | 2
3 | 5 | 1
4 | 1 | 3
4 | 2 | 2
I'm trying to create a query where I can select all the active users in the users table and have a column where I can see the status for "thing 3" from the User_to_thing table while also sorting results so that the nulls come at the end. So the result would be something like:
User ID | Status
----------------
1 | 1
2 | 2
5 | 1
3 | NULL
6 | NULL
What I have so far for a query is the following:
SELECT u1.id, u1.name, user_to_thing.status
FROM users u1
LEFT JOIN user_to_thing ON u1.id = user_to_thing.user_id
WHERE u1.active = 1
OR user_to_thing.event_id = 62
ORDER BY (CASE WHEN user_to_thing.status = 1 THEN 1
WHEN user_to_thing.status = 2 THEN 2
ELSE 3 END)
What I'm getting as a result is the following:
User ID | Status | Thing ID
---------------------------
1 | 1 | 3
1 | 3 | 4
2 | 2 | 3
2 | 2 | 4
5 | 1 | 3
3 | NULL | NULL
6 | NULL | NULL
I'm not sure how to limit it to just thing #3 while also getting a list of all active users. Any guidance would be appreciated.
It looks like the following should work for you, grouping to remove duplicates and ordering based on null
select u.Id as UserId, t.status
from users u
left join User_to_thing t on t.UserID = u.id
where u.active = 1
group by u.Id, t.Status
order by case when status is null then 1 else 0 end, u.Id
Based on your revised data, you can amend slightly
select u.Id UserId, Min(t.status) Status
from users u
left join User_to_thing t on t.UserID=u.id
where u.active=1
group by u.Id
order by case when Min(t.status) is null then 1 else 0 end, u.Id

show result of one table and find in other column

let me show table structure first then my question
suppose table A
id | url |
1 | page
2 | home
3 | product
4 | sell
and table b
id |user_id | table a id
1 | 1 | 1
the output which I want like
id | url | ispermission
1 | page | 1
2 | home | 0
3 | product | 0
4 | sell | 0
(value 1 )in ispermission mean that it map with user as you can see in table b other mean it does not have mapping
below is my query
select p.id,p.url,if(r.user_id=4,1,0) ispermission from
`table a` p
left join `table b` r on p.id=r.url_id
group by p.id,p.url order by ispermission DESC
this query is working great but in some case it got failed the result let conside that failed case the output which i m getting like (note this result is without groupby)
id | url | ispermission
1 | page | 0
1 | page | 1
2 | home | 0
3 | product | 0
4 | sell | 0
by using group by i m getting result like
id | url | ispermission
1 | page | 0
2 | home | 0
3 | product | 0
4 | sell | 0
Are you looking for something like this?
SELECT
T1.ID,
T1.Url,
COUNT(T2.[table a id]) Impression
FROM TableA T1
LEFT JOIN TableB T2 ON T1.ID = T2.[table a id]
GROUP BY T1.ID,T1.Url
Using LEFT JOIN with CASE expression:
select a.id, a.url,
case when b.tableAid is not null then 1 else 0 end as ispermission
from TableA a
left join TableB b on b.tableAid = a.id
Demo on db<>fiddle

Get a row with min(priority) from two tables

I need to query data from multiple tables, below are the major tables(simplified).
Project
+-----+-------+-------+
| pid | pname | status| //status: 0 = pending, 1 = complete
+-----+-------+-------+
| 1 | Proj1 | 0 |
| 2 | Proj2 | 1 |
| 3 | Proj3 | 0 |
+-----+-------+-------+
Module
+-----+--------+-------+----------+-----------------+
| mid | pid | status| priority |modulecategoryid |
+-----+--------+-------+----------+-----------------+
| 1 | 1 | 1 | 1 | 1 |
| 2 | 1 | 0 | 2 | 3 |
| 3 | 3 | 1 | 1 | 1 |
| 4 | 3 | 0 | 2 | 3 |
| 5 | 3 | 0 | 3 | 5 |
+-----+--------+-------+----------+-----------------+
Task
+----+--------+-------+----------+-----------------+
| id | mid | status| priority | taskcategoryid |
+----+--------+-------+----------+-----------------+
| 1 | 2 | 1 | 2 | 2 |
| 2 | 2 | 0 | 1 | 1 |
| 3 | 4 | 1 | 1 | 2 |
| 4 | 4 | 1 | 2 | 3 |
| 5 | 4 | 0 | 3 | 4 |
| 6 | 5 | 0 | 1 | 1 |
+----+--------+-------+----------+-----------------+
I am trying to get the pending tasks for all the pending projects that can be started first based on the module priority and task priority. i.e. for Proj3, module with priority 1 is completed so i should get first priority pending task for module 2.
I need to get the most prior task for each pending project with modulecategoryid and taskcategoryid for get its related info like this
+-----+--------+-----+------------------+----------------+
| pid | mid | tid | modulecategoryid | taskcategoryid |
+-----+--------+-----+------------------+----------------+
| 1 | 2 | 2 | 3 | 2 |
| 2 | 4 | 5 | 3 | 4 |
+----+---------+-----+------------------+----------------+
I am new to MySql and I have tried query with multiple joins and group it by projectids and min(priority) to get desired result. But columns that are not in group by are fetched randomly from the aggregate.
I have seen this answer SQL Select only rows with Max Value on a Column but that solves the problem for data in only one table.
Shall I get some help on that?
I can post my query if needed but it is getting wrong data.
SQL Select only rows with Max Value on a Column has the right approach. You just need to do it twice.
First create a subquery a showing the highest priority task for each module.
Then create a subquery b showing the highest priority Module for each project.
Then join your three tables and two subqueries together.
Here's a. It shows the highest priority Task id for each Module mid. (http://sqlfiddle.com/#!9/7eb1f3/4/0)
SELECT Task.id, Task.mid
FROM Task
JOIN (
SELECT MAX(priority) priority,
mid
FROM Task
WHERE status = 0
GROUP BY mid
) q ON q.priority = Task.priority AND q.mid = Task.mid
Here's b. It works the same way as a and shows the highest priority Module mid for each Project pid. (http://sqlfiddle.com/#!9/7eb1f3/3/0)
SELECT Module.mid, Module.pid
FROM Module
JOIN (
SELECT MAX(priority) priority,
pid
FROM Module
WHERE status = 0
GROUP BY pid
) q ON q.priority = Module.priority AND q.pid = Module.pid
Then you need a big JOIN to pull everything together. In outline it looks like this.
SELECT Project.pid, Project.pname,
Module.mid, Task.id tid,
Module.modulecategoryid, Task.taskcategoryid
FROM Project
JOIN ( /* the subquery called b */
) b ON Project.pid = b.pid
JOIN Module ON b.mid = Module.mid
JOIN ( /* the subquery called a */
) a ON Module.mid = a.mid
JOIN Task ON a.id = Task.id
WHERE Task.status = 0
The actual query looks like this, with the subqueries put in. (http://sqlfiddle.com/#!9/7eb1f3/2/0)
SELECT Project.pid, Project.pname,
Module.mid, Task.id tid,
Module.modulecategoryid, Task.taskcategoryid
FROM Project
JOIN (
SELECT Module.mid, Module.pid
FROM Module
JOIN (
SELECT MAX(priority) priority, pid
FROM Module
WHERE status = 0
GROUP BY pid
) q ON q.priority = Module.priority
AND q.pid = Module.pid
) b ON Project.pid = b.pid
JOIN Module ON b.mid = Module.mid
JOIN (
SELECT Task.id, Task.mid
FROM Task
JOIN (
SELECT MAX(priority) priority, mid
FROM Task
WHERE status = 0
GROUP BY mid
) q ON q.priority = Task.priority
AND q.mid = Task.mid
) a ON Module.mid = a.mid
JOIN Task ON a.id = Task.id
WHERE Task.status = 0
The secret to this is understanding that subqueries are virtual tables that you can join to each other or to ordinary tables. The skill you need is sorting out the combination of physical and virtual tables you need, and the join sequence.

Joining and nesting queries in mysql

Currently, I'm using this nice query:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(CASE WHEN race_results.place=1 THEN 1 ELSE 0 END) AS times_won_first_place
from users
inner join race_results
where race_results.userid = users.id and race_results.place = 1
group by users.id
order by total_winnings desc
to get this
************************************************
| name | total_winnings | times_won_first_place |
| Bob | 4000 | 4 |
| John | 1000 | 1 |
************************************************
the race_results table looks like this
*******************************************
| id | raceid | userid | place | winnings |
| 1 | 1 | 1 | 1 | 1000 |
| 2 | 1 | 2 | 5 | 50 |
| 3 | 1 | 3 | 6 | 50 |
| 4 | 2 | 1 | 1 | 1000 |
| 5 | 2 | 2 | 3 | 250 |
*******************************************
I would like to include four three more columns for something like this
***************************************************************************
| name | total_winnings | total_races | 1st_place | 2nd_place | 3rd_place |
| Bob | 4000 | 5 | 4 | 0 | 0 |
| John | 1000 | 5 | 1 | 1 | 1 |
***************************************************************************
If I were to do separate queries for the new columns, I'd use
select count(raceid) from race_results where userid = 1
select count(raceid) from race_results where userid = 1 and place = 1
select count(raceid) from race_results where userid = 1 and place = 2
select count(raceid) from race_results where userid = 1 and place = 3
to do separate queries would be easy but with the existing query I had to use CASE just to get the count of times a user won 1st place. (using
count(CASE WHEN race_results.place=2 THEN 1 ELSE 0 END)
returns the same results).
How would I nest these or join them into my existing query to get what I want?
You can do it this way:
select
users.name,
sum(race_results.winnings) as total_winnings,
count(*) AS total_races,
sum(race_results.place = 1) AS times_won_first_place ,
sum(race_results.place = 2) AS times_won_second_place,
sum(race_results.place = 3) AS times_won_third_place
from users
inner join race_results
where race_results.userid = users.id
group by users.id
order by total_winnings desc;
With ANSI standard SQL you could use case expressions inside the sum function but since MySQL (and some other databases) evaluate boolean expressions to 1 for true you can replace the case expression with the just the condition to evaluate and then just sum them.
So instead of CASE WHEN race_results.place=1 THEN 1 ELSE 0 END you can do sum(race_results.place=1) and save some space and typing :)
See this SQL Fiddle for an example.

Count rows with specific value over multiple rows

Its very hard for to set a proper title, because I dont know how I describe my problem.
I have a table like this:
dlID | dl_seID | dlEpisode | dlFlag
___________________________________
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 1 | 3 | 1
4 | 2 | 1 | 1
5 | 2 | 2 | 0
6 | 3 | 1 | 0
What i want is a select query where I get something like this:
dlID | dl_seID | dlEpisode | dlFlag | dlFlagCount
_________________________________________________
1 | 1 | 1 | 0 | 2
2 | 1 | 2 | 1 | 2
3 | 1 | 3 | 1 | 2
4 | 2 | 1 | 1 | 1
5 | 2 | 2 | 0 | 1
6 | 3 | 1 | 0 | 0
dlFlagCount shoud be a counter of dlFlag = 1 where dl_seID = dl_seID.
Second try:
I need a value where I see how many Flags have the value 1 with the same dl_seID.
Is that possible?
I hope you guys know what I want^^
Regards
Try this:
select
a.*,
ifnull(b.ctflags,0)
from
tablea a left join
( select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID ) b on (a.dl_seID = b.dl_seID)
The left join is just to get the registry with 0 flags
See the fiddle: http://sqlfiddle.com/#!2/ef9b0/5
EDIT:
As op requested some explanation, here it goes:
What you asked is to count the amount of flags by the dl_seID and to do that you need to do this you separeta your problems, first you get the count for the dl_seID by flags, this is this subquery:
select dl_seID, count(dlFlag) ctflags
from tablea
where dlFlag=1
group by dl_seID
This became a 'separe table' or a new group of data, whatever you wanna call it. Then you have to join this with your original data (from your table) like the query for answer.
The left join part is because maybe there are some data that wont complain with where dlFlag=1 therefore if you want to get then as 0 you have to bring all values from table that exists or not on our created subgroup. And this ifnull(b.ctflags,0) is for theese data data exists on your table but has no flags (for your problem). If you use just b.ctflags it will bring null.
SELECT x.*
, COALESCE(y.flagcount,0) flagcount
FROM my_table x
LEFT
JOIN
( SELECT seID
, COUNT(*) flagcount
FROM my_table
WHERE flag = 1
GROUP
BY seid
) y
ON y.seid = x.seid;