Using parent SQL column in subquery - mysql

Good morning. I'm trying to pull the username of the user from the column in to_id. Is there. It'd be simple if I was just filtering on to_id, but I also need records from another column from_id. I've attempted doing a UNION to get around this issue, but it only pulls records from user.id 3 of course.
Does anyone happen to know a way around this? I'm somewhat new to writing complex SQL queries. Haven't been able to figure much out from similar questions.
SELECT
users.username, -- Placeholder until username from to_id can be pulled
payment.id,
to_id,
amount,
state,
type,
timedate
FROM
payment
LEFT JOIN users ON users.id = payment.to_id AND users.id = payment.from_id
WHERE to_id = 3 OR from_id = 3
The result of that would be along the lines of:
+----------+----+-------+--------+----------+------+---------------------+
| username | id | to_id | amount | state | type | timedate |
+----------+----+-------+--------+----------+------+---------------------+
| NULL | 1 | 1 | 12.56 | COMPLETE | u2u | 2021-11-12 06:09:21 |
| NULL | 2 | 1 | 43.00 | COMPLETE | u2u | 2021-11-12 06:17:10 |
| NULL | 3 | 3 | 2.25 | COMPLETE | u2u | 2021-11-12 06:22:53 |
+----------+----+-------+--------+----------+------+---------------------+
Username is null due to the two Joins being AND. If it's OR, the username will show up, but the rows will be there twice. Once with the to_id username, once with the from_id username.

So you have one users table for all payers and payees accounts and one transaction table with two ID columns (payer and payee)? You need to join the users table to the transaction table twice, once to get the payer info, once to get the payee info.
select
payment.from_id,
from_user.username,
payment.to_id,
to_user.username,
payment.id,
amount,
state,
type,
timedate
from payment
left join users as from_user
on from_user.id = payment.from_id
left join users as to_user
on to_user.id = payment.to_id
where payment.to_id = 3 OR payment.from_id = 3

Related

Merge based on "group by" groups

So I have a table called the Activities table that contains a schema of user_id, activity
There is a row for each user, activity combo.
Here is a what it might look like (empty rows added to make things easier to look at, please ignore):
| user_id | activity |
|---------|-----------|
| 1 | swimming | -- We want to match this
| 1 | running | -- person's activities
| | |
| 2 | swimming |
| 2 | running |
| 2 | rowing |
| | |
| 3 | swimming |
| | |
| 4 | skydiving |
| 4 | running |
| 4 | swimming |
I would like to basically find all other users with at least the same activities as a given input id so that I could recommend users with similar activities.
so in the table above, if I wanna find recommended users for user_id=1, the query would return user_id=2 and user_id=4 because they engage in both swimming, running (and more), but not user_id=3 because they only engage in swimming
So a result with a single column of:
| user_id |
|---------|
| 2 |
| 4 |
is what I would ideally be looking for
As far as what I've tried, I am kinda stuck at how to get a solid set of user_id=1's activities to match against. Basically I'm looking for something along the lines of:
SELECT user_id from Activities
GROUP BY user_id
HAVING input_user_activities in user_x_activities
where user1_activities is just a set of our input user's activities. I can create that set using a WITH input_user_activities AS (...) in the beginning, what I'm stuck at is the user_x_activities part
Any thoughts?
To get users with the same activities, you can use a self join. Let me assume that the rows are unique:
select a.user_id
from activities a1 join
activities a
on a1.activity = a.activity and
a1.user_id = #user_id
group by a.user_id
having count(*) = (select count(*) from activities a1 where a1.user_id = #user_id);
The having clause answers your question -- of getting users that have the same activities as a given user.
You can easily get all users ordered by similarity using a JOIN (that finds all common rows) and a GROUP BY (to summarize the similarity per user_id) and finally an ORDER BY to return the most similar users first.
SELECT b.user_id, COUNT(*) similarity
FROM activities a
JOIN activities b
ON a.activity = b.activity
WHERE a.user_id = 1 AND b.user_id != 1
GROUP BY b.user_id
ORDER BY COUNT(*) DESC
An SQLfiddle to test with.

Mysql: Group by with condition

I've searched on my question but I couldn't really find what I was looking for or maybe I just didn't understand the examples. If there is a similar post please point me to the right thread.
What I'm trying to do is the following: I have results like the table below which I generated with a very simple query:
SELECT id, first_name, last_name, email, roles, created
FROM user
As you can see a user can have two roles: User or teacher. Some persons are only teacher and some are only user. However, some of them are both teacher and user.
Now I want to group by e-mail adres, but of course this doesn't work on persons who are both user and teacher.
I would like to group by e-mail and in case a person has both roles I want to keep the user role in the results. I understood this can be done with an if condition but I can't figure out where or how to do it.
+------+------------+-----------+-----------------------+--------------+
| id | first_name | last_name | email | roles |
+------+------------+-----------+-----------------------+--------------+
| 9798 | person | one | personOne#gmail.com | ROLE_USER |
| 9800 | person | one | personOne#gmail.com | ROLE_TEACHER |
| 9801 | person | two | personTwo#gmail.com | ROLE_TEACHER |
| 9802 | person | three | personThree#gmail.com | ROLE_TEACHER |
| 9803 | person | four | personFour#gmail.com | ROLE_USER |
+------+------------+-----------+-----------------------+--------------+
So my query should be something like this:
SELECT id, first_name, last_name, email, roles, created
FROM user
group by email (if count(email) > 1 "ROLE_USER from roles should end up in results")
Could anybody point me in the right direction or make an example? Thanks so much!
If there are only 2 roles you could
select * from t where roles = 'role_user'
union
select * from t where roles = 'role_teacher' and
((select count(*) from t t1 where t1.email = t.email) = 1)
order by id;
+------+------------+-----------+-----------------------+--------------+
| id | first_name | last_name | email | roles |
+------+------------+-----------+-----------------------+--------------+
| 9798 | person | one | personOne#gmail.com | ROLE_USER |
| 9801 | person | two | personTwo#gmail.com | ROLE_TEACHER |
| 9802 | person | three | personThree#gmail.com | ROLE_TEACHER |
| 9803 | person | four | personFour#gmail.com | ROLE_USER |
+------+------------+-----------+-----------------------+--------------+
4 rows in set (0.03 sec)
But this won't work if there are roles that are duplicated for an email.
it is probably easier than you think, but like you said, being new, might not have understood. That said, and trying to interpret other commands not to your data scenario is a little harder. You know you have the post possible combinations of 3... User, Teacher or both. I would just add a column to represent each possible grouped by email. Now, being that you are grouping by email, do you still need the "ID", and "created" fields? I'm not sure, but we'll throw those in too just in case.
select
u.email
max( u.first_name ) first_name,
max( u.last_name ) last_name,
max( case when u.roles = 'ROLE_USER' then 1 else 0 end ) IsUserRole,
max( case when u.roles = 'ROLE_TEACHER' then 1 else 0 end ) IsTeacherRole
from
user u
group by
u.email
By applying a max, it for the name, if you had a person whose name changed, or had a mis-entry into the system, you would just get one, but if names were the same, it does not matter. As for the User / Teacher roles, I am just returning a 1 if the record returns true, otherwise a zero. This SHOULD get you what you need.
if i understand the question right something like this should help you
SELECT u.id,u.email, u.fname, u.llname, group_concat(r.role) FROM user u
LEFT OUTER JOIN user r ON (u.email = r.email) GROUP BY u.email

Can I be selective on what rows I join on in MySQL

Suppose I have two tables, people and emails. emails has a person_id, an address, and an is_primary:
people:
id
emails:
person_id
address
is_primary
To get all email addresses per person, I can do a simple join:
select * from people join emails on people.id = emails.person_id
What if I only want (at most) one row from the right table for each row in the left table? And, if a particular person has multiple emails and one is marked as is_primary, is there a way to prefer which row to use when joining?
So, if I have
people: emails:
------ -----------------------------------------
| id | | id | person_id | address | is_primary |
------ -----------------------------------------
| 1 | | 1 | 1 | a#b.c | true |
| 2 | | 2 | 1 | b#b.c | false |
| 3 | | 3 | 2 | c#b.c | true |
| 4 | | 4 | 4 | d#b.c | false |
------ -----------------------------------------
is there a way to get this result:
------------------------------------------------
| people.id | emails.id | address | is_primary |
------------------------------------------------
| 1 | 1 | a#b.c | true |
| 2 | 3 | c#b.c | true | // chosen over b#b.c because it's primary
| 3 | null | null | null | // no email for person 3
| 4 | 4 | d#b.c | false | // no primary email for person 4
------------------------------------------------
You got it a bit wrong, how left/right joins work.
This join
select * from people join emails on people.id = emails.person_id
will get you every column from both tables for all records that match your ON condition.
The left join
select * from people left join emails on people.id = emails.person_id
will give you every record from people, regardless if there's a corresponding record in emails or not. When there's not, the columns from the emails table will just be NULL.
If a person has multiple emails, multiple records will be in the result for this person. Beginners often wonder then, why the data has duplicated.
If you want to restrict the data to the rows where is_primary has the value 1, you can do so in the WHERE clause when you're doing an inner join (your first query, although you ommitted the inner keyword).
When you have a left/right join query, you have to put this filter in the ON clause. If you would put it in the WHERE clause, you would turn the left/right join into an inner join implicitly, because the WHERE clause would filter the NULL rows that I mentioned above. Or you could write the query like this:
select * from people left join emails on people.id = emails.person_id
where (emails.is_primary = 1 or emails.is_primary is null)
EDIT after clarification:
Paul Spiegel's answer is good, therefore my upvote, but I'm not sure if it performs well, since it has a dependent subquery. So I created this query. It may depend on your data though. Try both answers.
select
p.*,
coalesce(e1.address, e2.address) AS address
from people p
left join emails e1 on p.id = e1.person_id and e1.is_primary = 1
left join (
select person_id, address
from emails e
where id = (select min(id) from emails where emails.is_primary = 0 and emails.person_id = e.person_id)
) e2 on p.id = e2.person_id
Use a correlated subquery with LIMIT 1 in the ON clause of the LEFT JOIN:
select *
from people p
left join emails e
on e.person_id = p.id
and e.id = (
select e1.id
from emails e1
where e1.person_id = e.person_id
order by e1.is_primary desc, -- true first
e1.id -- If e1.is_primary is ambiguous
limit 1
)
order by p.id
sqlfiddle

Optimization SQL for getting data from two joined tables (usernames for user-from-id and user-to-id msgs from two tables)

I have table "msgs" with messages between users (their ids):
+--------+-------------+------------+---------+---------+
| msg_id |user_from_id | user_to_id | message | room_id |
+--------+-------------+------------+---------+---------+
| 1 | 1 | 4 |Hello! | 2 |
| 2 | 1 | 5 |Hi there | 1 |
| 3 | 2 | 1 |CU soon | 2 |
| 4 | 3 | 7 |nice... | 1 |
+--------+-------------+------------+---------+---------+
I also have two tables with users names.
Table: user1
+--------+----------+
|user_id |user_name |
+--------+----------+
| 5 | Ann |
| 6 | Sam |
| 7 | Michael |
+--------+----------+
Table: user2
+--------+----------+
|user_id |user_name |
+--------+----------+
| 1 | John |
| 2 | Alice |
| 3 | Tom |
| 4 | Jane |
+--------+----------+
I need to get usernames for two users IDs in every row. Every user-id can be in first or second table with usernames.
I wrote this SQL query:
SELECT DISTINCT
m.msg_id,
m.user_from_id,
CASE WHEN c1.user_name IS NULL THEN c3.user_name ELSE c1.user_name END AS from_name,
m.user_to_id,
CASE WHEN c2.user_name IS NULL THEN c4.user_name ELSE c2.user_name END AS to_name,
m.message
FROM msgs m
LEFT JOIN users1 c1 ON c1.user_id=m.user_from_id
LEFT JOIN users1 c2 ON c2.user_id=m.user_to_id
LEFT JOIN users2 c3 ON c3.user_id=m.user_from_id
LEFT JOIN users2 c4 ON c4.user_id=m.user_to_id
WHERE m.room_id=1
LIMIT 0, 8
It works.
Execute query to get raw data without usernames (without any join) tooks about ~0.1 sec. But it's enough to join only one usernames table (user1 or user2 only) to get this data in about ~6.2 sec. (with join one table). I have quite a lot rows in this tables: 35K rows in msgs, 0.5K in user1, 25K in user2.
Executing query with join two tables (with all this data) is impossible.
How to optimize this query? I just need usernames for user_ids in first "msgs" table.
There are potentially many differences between the queries with and without the joins. I am going to assume that the ids have the appropriate indexes -- primary keys automatically do. If not, then check that.
The obvious solution is to use the original query as a subquery:
SELECT m.msg_id, m.user_from_id,
(CASE WHEN c1.user_name IS NULL THEN c3.user_name ELSE c1.user_name
END) AS from_name,
m.user_to_id,
(CASE WHEN c2.user_name IS NULL THEN c4.user_name ELSE c2.user_name
END) AS to_name,
m.message
FROM (SELECT m.*
FROM msgs m
WHERE m.room_id = 1
LIMIT 0, 8
) m LEFT JOIN
users1 c1
ON c1.user_id = m.user_from_id LEFT JOIN
users1 c2
ON c2.user_id = m.user_to_id LEFT JOIN
users2 c3
ON c3.user_id = m.user_from_id LEFT JOIN
users2 c4
ON c4.user_id = m.user_to_id;
For most data structures, the distinct is also unnecessary.
This also makes (the reasonable assumption) that user_id is unique in the users tables.
Also, use of LIMIT without ORDER BY is highly discouraged. The particular rows you get are indeterminate and might change from one execution to the next.

MySQL SELECT Multiple DISTINCT COUNT

Here is what I'm trying to do. I have a table with user assessments which may contain duplicate rows. I'm looking to only get DISTINCT values for each user.
In the example of the table below. If only user_id 1 and 50 belongs to the specific location, then only the unique video_id's for each user should be returned as the COUNT. User 1 passed video 1, 2, and 1. So that should only be 2 records, and user 50 passed video 2. So the total for this location would be 3. I think I need to have two DISTINCT's in the query, but am not sure how to do this.
+-----+----------+----------+
| id | video_id | user_id |
+-----+----------+----------+
| 1 | 1 | 1 |
| 2 | 2 | 50 |
| 3 | 1 | 115 |
| 4 | 2 | 25 |
| 5 | 2 | 1 |
| 6 | 6 | 98 |
| 7 | 1 | 1 |
+-----+----------+----------+
This is what my current query looks like.
$stmt2 = $dbConn->prepare("SELECT COUNT(DISTINCT user_assessment.id)
FROM user_assessment
LEFT JOIN user ON user_assessment.user_id = user.id
WHERE user.location = '$location'");
$stmt2->execute();
$stmt2->bind_result($video_count);
$stmt2->fetch();
$stmt2->close();
So my query returns all of the count for that specific location, but it doesn't omit the non-unique results from each specific user.
Hope this makes sense, thanks for the help.
SELECT COUNT(DISTINCT ua.video_id, ua.user_id)
FROM user_assessment ua
INNER JOIN user ON ua.user_id = user.id
WHERE user.location = '$location'
You can write a lot of things inside a COUNT so don't hesitate to put what you exactly want in it. This will give the number of different couple (video_id, user_id), which is what you wanted if I understood correctly.
The query below joins a sub-query that fetches the distinct videos per user. Then, the main query does a sum on those numbers to get the total of videos for the location.
SELECT
SUM(video_count)
FROM
user u
INNER JOIN
( SELECT
ua.user_id,
COUNT(DISTINCT video_id) as video_count
FROM
user_assessment ua
GROUP BY
ua.user_id) uav on uav.user_id = u.user_id
WHERE
u.location = '$location'
Note, that since you already use bindings, you can also pass $location in a bind parameter. I leave this to you, since it's not part of the question. ;-)