How do I run this MySQL JOIN query? - mysql

Let's say I have 2 tables.
The first table is a list of personas. A user can have many personas.
mysql> select id, user_id, name from personas_personas;
+----+---------+--------------+
| id | user_id | name |
+----+---------+--------------+
| 8 | 1 | startup |
| 9 | 1 | nerd |
| 10 | 1 | close |
| 12 | 2 | Nerd |
| 13 | 2 | Startup |
| 14 | 2 | Photographer |
+----+---------+--------------+
6 rows in set (0.00 sec)
Now, I have another table called "approvals".
mysql> select id, from_user_id, to_user_id, persona_id from friends_approvals;
+----+--------------+------------+------------+
| id | from_user_id | to_user_id | persona_id |
+----+--------------+------------+------------+
| 2 | 1 | 2 | 8 |
| 3 | 1 | 2 | 9 |
+----+--------------+------------+------------+
2 rows in set (0.00 sec)
If from_user wants to approve to_user to a persona, then a record is inserted.
I'm trying to do this query...
Given a user, find all its personas. Then, for each persona, determine if it's approved for a certain to_user. If so, return is_approved=1 in the result set. Otherwise, return is_approved=0 in the result set.
So this is where I start:
SELECT *
FROM personas_personas
WHERE user_id = 1
LEFT JOIN friends_approvals ON
...but i don't know where to go from here.
So, the final result set should have all the columns in the personas_personas table, and then also is_approved for each of the results.

SELECT
pp.*,
CASE
WHEN exists (
SELECT
*
FROM
friends_approvals fa
WHERE
fa.from_user_id = pp.user_id AND
fa.persona_id = pp.id AND
fa.to_user_id = 2
)
THEN 1
ELSE 0
END as is_approved
FROM
personas_personas pp
WHERE
pp.user_id=1
Or, depending on your taste:
SELECT
pp.*,
CASE
WHEN fa.from_user_id IS NOT NULL
THEN 1
ELSE 0
END as is_approved
FROM
personas_personas pp
LEFT OUTER JOIN friends_approvals fa ON
pp.user_id = fa.from_user_id AND
pp.id = fa.persona_id AND
fa.to_user_id = 2
WHERE
pp.user_id=1

If I'm understanding your needs correctly, you can do this:
SELECT personas_personas.*,
CASE WHEN friends_approvals IS NULL THEN 0 ELSE 1 END AS is_approved
FROM personas_personas
LEFT
OUTER
JOIN friends_approvals
ON friends_approvals.from_user_id = ...
AND friends_approvals.to_user_id = personas_personas.user_id
AND friends_approvals.persona_id = personas_personas.id
WHERE personas_personas.user_id = ...
;
That will find every personas_personas record with the specified user_id, together with an indicator of whether that user, in that persona, has been "approved" by a specified from_user_id.
(If that's not what you want, then please clarify!)

Related

Get all person rows when left joined values missing [duplicate]

I have two tables and need to get all rows from the first one and then check which values from the second table match the first table.
My goal is to get all so called 'achievements' and then check which one the user has reached.
achievements
+---------------+-------------+
| achievementID | description |
+---------------+-------------+
| 1 | goal1 |
| 2 | goal2 |
| 3 | goal3 |
+---------------+-------------+
achievement_user
+---------------------+---------------+--------+
| achievementRecordID | achievementID | userID |
+---------------------+---------------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 3 |
| 3 | 4 | 2 |
| 4 | 3 | 1 |
+---------------------+---------------+--------+
My desired result for a query where I check the results for userID = 1 would be something like
+---------------+---------------+--------------+
| achievementID | description | solvedByUser |
+---------------+---------------+--------------+
| 1 | goal1 | true |
| 2 | goal2 | false |
| 3 | goal3 | true |
+---------------+---------------+--------------+
The new column solvedByUser could be basically any datatype (boolean, int, ...).
I just need a list of all available achievements and then see which one the user has reached.
You can left join the achievments table with achievement_user:
select a.*, (au.userID is not null) solvedByUser
from achievements a
left join achievement_user au
on au.achievementID = a.achievementID
and au.userID = 1
solvedByUser is a 0/1 flag that indicates whether the given achievement was reached by the given user.
I think you need a left join -
SELECT A.achievementID, A.description, CASE WHEN AU.userID IS NOT NULL THEN 'true' ELSE 'false' solvedByUser
FROM achievements A
LEFT JOIN achievement_user AU ON A.achievementID = AU.achievementID
AND userID = 1; -- YOUR INPUT ID
You need a left join:
select a.*,
case when u.achievementID is null then 'false' else 'true' end solvedByUser
from achievements a left join achievement_user u
on u.achievementID = a.achievementID and u.userid = 1

Query to get subjects of interest for all User Y where Y shares >=3 interests with a User X

These are two tables from a part of supposed Twitter like database where users can follow other users. The User.name field is unique.
mysql> select uID, name from User;
+-----+-------------------+
| uID | name |
+-----+-------------------+
| 1 | Alice |
| 2 | Bob |
| 5 | Iron Maiden |
| 4 | Judas Priest |
| 6 | Lesser Known Band |
| 3 | Metallica |
+-----+-------------------+
6 rows in set (0.00 sec)
mysql> select * from Follower;
+-----------+------------+
| subjectID | observerID |
+-----------+------------+
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 3 | 2 |
| 4 | 2 |
| 5 | 2 |
+-----------+------------+
7 rows in set (0.00 sec)
mysql> call newFollowSuggestionsForName('Bob');
+-------------------+
| name |
+-------------------+
| Lesser Known Band |
+-------------------+
1 row in set (0.00 sec)
I want to make an operation that will suggest for a user X a list of users they may be interested in following. I thought one heuristic could be to show X for all y who user y follows where X and y follow at least 3 of the same Users. Below is the SQL I came up with to do this. My question is if it could be done more efficiently or nicer in some other ways.
DELIMITER //
CREATE PROCEDURE newFollowSuggestionsForName(IN in_name CHAR(60))
BEGIN
DECLARE xuid INT;
SET xuid = (select uID from User where name=in_name);
select name
from User, (select subjectID
from follower
where observerID in (
select observerID
from Follower
where observerID<>xuid and subjectID in (select subjectID from Follower where observerID=xuid)
group by observerID
having count(*)>=3
)
) as T
where uID = T.subjectID and not exists (select * from Follower where subjectID=T.subjectID and observerID=xuid);
END //
DELIMITER ;
Consider the following refactored SQL code (untested without data) for use in stored procedure.
select u.`name`
from `User` u
inner join
(select subf.observerID, subf.subjectID
from follower subf
where subf.observerID <> xuid
) f
on u.UID = f.subjectID
inner join
(select f1.observerID
from follower f1
inner join follower f2
on f1.subjectID = f2.subjectID
and f1.observerID <> xuid
and f2.observerID = xuid
group by f1.observerID
having count(*) >= 3
) o
on f.observerID = o.observerID
I think the basic query starts as getting all "observers" who share three "subjects" with a given observer:
select f.observerid
from followers f join
followers f2
on f.subjectid = f2.subjectid and
f2.observerid = 2
group by f.observerid
having count(*) = 3;
The rest of the query is just joining in the names to fit into your paradigm of using names for references rather than ids.

Select values if doesn't match in other table by left join

I am having some problem with getting the values from two tables of my database .
I have two tables in my database 1 is mem and 2nd is payment
mem stores name and drawid of users
payment table stores draw and instalment of the user
User pays us every month .
so if a user with a draw id 1 pays us in feb the values in two tables are
mem drawid=1 and name = something
payment draw = 1 and instalment=2
drawid in mem is same as draw in payment
so the tables has many to many relation.
Now I need to find the list of all the members who have not paid even 1 instalment before 4th month.
I am using this query
SELECT drawid,contact,dnd,mem.name, count(*) as numPayments FROM mem
LEFT JOIN payment ON (mem.drawid = payment.draw) GROUP BY
drawid HAVING numPayments < 4
it's working all good no issues only the issue is that I also need to show the instalments the user has paid so I need to fetch all the instalment from table payments and then show by while loop.
This query is perfect but it gives me repeated results !!!
SELECT drawid,contact,dnd,mem.name, count(*) as numPayments,NULL numPaidPayments ,NULL PAID_CONTACT,NULL NAME_PAID FROM mem
LEFT JOIN payment ON (mem.drawid = payment.draw) GROUP BY
drawid HAVING numPayments < 4
UNION
SELECT NULL drawid,NULL contact, NULL dnd, NULL name,NULL numPayments,COUNT(*) as numPaidPayments ,contact PAID_CONTACT,mem.name NAME_PAID FROM mem
INNER JOIN payment ON (mem.drawid = payment.draw) GROUP BY
drawid HAVING numPaidPayments >= 4
SELECT drawid,contact,dnd,mem.name, count(*) as numPayments,NULL numPaidPayments ,NULL PAID_CONTACT,NULL NAME_PAID FROM mem
LEFT JOIN payment ON (mem.drawid = payment.draw) GROUP BY
drawid HAVING numPayments < 4
UNION
SELECT NULL drawid,NULL contact, NULL dnd, NULL name,NULL numPayments,COUNT(*) as numPaidPayments ,contact PAID_CONTACT,mem.name NAME_PAID FROM mem
INNER JOIN payment ON (mem.drawid = payment.draw) GROUP BY
drawid HAVING numPaidPayments >= 4
Try above query.
Given this
MariaDB [sandbox]> select * from member;
+------+
| id |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
+------+
4 rows in set (0.00 sec)
MariaDB [sandbox]> select * from payment;
+--------+------+
| mem_id | mth |
+--------+------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 2 | 1 |
| 2 | 3 |
| 3 | 2 |
| 3 | 4 |
+--------+------+
8 rows in set (0.00 sec)
This
select m.id, group_concat(p.mth order by p.mth) mthspaid,
sum(case when p.mem_id is not null then 1 else 0 end) NoofMthsPaid,
4 - sum(case when p.mem_id is not null then 1 else 0 end) NoofMthsMissed
from member m
left join payment p on p.mem_id = m.id
group by m.id
Gives this
+------+----------+--------------+----------------+
| id | mthspaid | NoofMthsPaid | NoofMthsMissed |
+------+----------+--------------+----------------+
| 1 | 1,2,3,4 | 4 | 0 |
| 2 | 1,3 | 2 | 2 |
| 3 | 2,4 | 2 | 2 |
| 4 | NULL | 0 | 4 |
+------+----------+--------------+----------------+
4 rows in set (0.00 sec)
And if you add in code to work out the months due
select paid.*, due.mthsmissed
from
(
select m.id, group_concat(p.mth order by p.mth) mthspaid,
sum(case when p.mem_id is not null then 1 else 0 end) NoofMthsPaid,
4 - sum(case when p.mem_id is not null then 1 else 0 end) NoofMthsMissed
from member m
left join payment p on p.mem_id = m.id
group by m.id
) paid
left join
(
select due.id, group_concat(due.mth order by due.mth) MthsMIssed
from
(
select m.id,d.mth
from member m,(select 1 mth union select 2 union select 3 union select 4) d
) due
left join payment p on p.mem_id = due.id and p.mth = due.mth
where p.mth is null
group by due.id
) due on paid.id = due.id
You get this
+------+----------+--------------+----------------+------------+
| id | mthspaid | NoofMthsPaid | NoofMthsMissed | mthsmissed |
+------+----------+--------------+----------------+------------+
| 1 | 1,2,3,4 | 4 | 0 | NULL |
| 2 | 1,3 | 2 | 2 | 2,4 |
| 3 | 2,4 | 2 | 2 | 1,3 |
| 4 | NULL | 0 | 4 | 1,2,3,4 |
+------+----------+--------------+----------------+------------+
4 rows in set (0.04 sec)

Querying MySQL tables for a item a user hasnt 'scored' yet

Tables
__________________ ________________________________
|______name________| |____________scores______________|
|___id___|__name___| |_id_|_user-id_|_name-id_|_score_|
| 1 | bob | | 1 | 3 | 1 | 5 |
| 2 | susan | | 2 | 1 | 3 | 4 |
| 3 | geoff | | 3 | 3 | 2 | 3 |
| 4 | larry | | 4 | 2 | 4 | 5 |
| 5 | peter | | 5 | 1 | 1 | 0 |
-------------------- ----------------------------------
Im looking to write a query that returns a RANDOM name from the 'name' table, that the user hasnt scored so far.
So given user '1' for example, it could return 'susan, larry or peter' as user '1' hasnt given them a score yet.
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id
WHERE votes.user_id = 1
AND (votes.score IS NULL);
So far I have this, but it doesnt seem to be working as I would like
(atm it doesnt return a random, but all, but this is wrong)
Any help would be appreciated.
If you are filtering on some field of outer joined table type of join is automatically changed to inner. In your case it's condition
votes.user_id = 1
So you need to move that condition from WHERE to ON
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id and votes.user_id = 1
WHERE (votes.score IS NULL);
Consider moving the condition from WHERE to JOIN ON clause since you are performing an OUTER JOIN else the effect would be same as INNER JOIN
LEFT JOIN votes
ON names.id = votes.name_id
AND votes.user_id = 1
WHERE votes.score IS NULL
ORDER BY RAND();
You could apply :
SELECT name FROM name join scores on name.id=scores.user_id WHERE scores.score=0
You can perform this as a sub-query
SELECT *
FROM names
WHERE id NOT IN (SELECT name_id FROM votes WHERE user_id=1)
ORDER BY RAND()
LIMIT 1

Get records from one table and a corresponding table

I have two tables:
orders
poid | user | pid | payment_id
1 | 1 | 1 | abc123
2 | 2 | 2 | def345
orders_addon
poaid | user | poid | pid
1 | 1 | 1 | 3
2 | 1 | 1 | 5
One represents orders, the second one represent addons a user can add to his order.
There is always a row in orders and it can occur that there is no matching orders_addon for an order.
I'm looking for a query that returns matching rows from orders and orders_addon if there are matching ones.
SELECT user,pid FROM ... WHERE payment_id = 'abc123'
Should return
user | pid
1 | 1
1 | 3
1 | 5
And the same query should only return results from the orders table if there is no matching record in the orders_addon table.
SELECT user,pid FROM ... WHERE payment_id = 'def345'
user | pid
2 | 2
I reckon this could be done using UNION but then I wouldn't be able to match the tables and it would become a problem since the orders_addon table doesn't have a payment_id
Use LEFT JOIN WITH IF STATMENT
mysql> ( SELECT u.user,IFNULL(ua.pid ,u.pid) as pid
FROM orders u
inner JOIN orders_addon ua on ua.poid=u.poid
WHERE u.payment_id = 'abc123'
)
union all
( SELECT u.user,u.pid
from orders u
where u.payment_id = 'def345'
);
+------+------+
| user | pid |
+------+------+
| 1 | 3 |
| 1 | 5 |
| 2 | 2 |
+------+------+
3 rows in set (0.00 sec)
mysql> ( SELECT u.user,IFNULL(ua.pid ,u.pid) as pid
FROM orders u
inner JOIN orders_addon ua on ua.poid=u.poid
WHERE u.payment_id = 'def345'
)
union all
( SELECT u.user,u.pid
from orders u
where u.payment_id = 'def345'
);
+------+------+
| user | pid |
+------+------+
| 2 | 2 |
+------+------+
1 row in set (0.00 sec)