I have 3 tables which are divided in users, user_courses and course.
I wanted to create 2 queries:
One query to return the subscribed courses of a specific user
Another query to return the unsubscribes courses of a specific user
I got the first query done but I canĀ“t make the 2nd query work.
I tried to negate the conditions and got the same rows multiple times.
SELECT
course.course_id,course_name,course_owner,course_date,course_time
FROM
course, users, user_courses
WHERE
usershortcut = "mmuster"
AND user_courses.user_id = users.user_id
AND user_courses.course_id = course.course_id
This is the first query which gives out all the subscribed courses of a user and I have no clue how to reverse it so I get the unsub courses of that user.
If the query you mention gives you the subscribed courses, for the second one you can use a filtering condition as in:
select *
from course
where course_id not in (
SELECT course.course_id
FROM course,users,user_courses
WHERE usershortcut="mmuster" and user_courses.user_id = users.user_id
and user_courses.course_id = course.course_id
)
Related
I have a Laravel booking app but am currently doing some manual reporting for a client.
I have a SQL query I run in SequelPro:
SELECT t.name
, t.email
, t.trial_ends_at
,
FROM teams t
ORDER
BY t.trial_ends_at DESC
However, what I now wish to do is add another field to each row that shows the client count for that team.
The relationships for clients of a team is:
users can have many bookings,
bookings belong to a user,
bookings have a team_id field
What I wish to do is append the count of users where they have made at least 1 booking of that team id.
In Laravel's eloquent I would do:
return User::whereHas('bookings', function($q) {
$q->where('team_id', THE ID)
})->count();
Join to a subquery which finds the counts:
SELECT
t.name,
t.email,
t.trial_ends_at,
COALESCE(b.cnt, 0) AS client_cnt
FROM teams t
LEFT JOIN
(
SELECT team_id, COUNT(*) AS cnt
FROM bookings
GROUP BY team_id
) b
ON t.id = b.team_id -- this assumes that id joins to team_id
ORDER BY
t.trial_ends_at DESC;
You had the following requirement:
What I wish to do is append the count of users where they have made at least 1 booking of that team id.
It seems to me that a user would only appear in a record in the bookings table if there was a reservation associated with that record. In other words, I don't think we need to do any extra check for this requirement, since if a user does appear, then by default he already appeared once.
If your first query produce $teams then you could do something like-
foreach($teams as $team){
$userCount = User::whereHas('bookings', function($q) use($team){
$q->where('team_id', $team->id);
})->count();
if($userCount>0){
$team->userCount = $userCount;
}
}
This will append userCount if you have more then single booking in the $teams collections, otherwise not.
I manage a property website. I have a table with banned users (small table) and a table called advert_views which keeps track of each listing that each user views (currently 1.3m lines and growing). The advert_views table alsio takes note of the IP address for every advert viewed).
I want to get the IP addresses used by the banned users and check if any of these banned users have opened new accounts. I ran the following query:
SELECT adviews.user_id AS 'banned user_id',
adviews.client_ip AS 'IPs used by banned users',
adviews2.user_id AS 'banned users that opened a new account'
FROM banned_users
LEFT JOIN users on users.email_address = banned_users.email_address #since I don't store the user_id in banned_users
LEFT JOIN advert_views adviews ON adviews.user_id = users.id AND adviews.user_id IS NOT NULL # users may view listings when not logged in but they have restricted access to the information on the listing
LEFT JOIN (SELECT client_ip,
user_id
FROM advert_views
WHERE user_id IS NOT NULL
) adviews2
ON adviews2.client_ip = adviews.client_ip
WHERE banned_users.rec_status = 1 and adviews.user_id <> adviews2.user_id
GROUP BY adviews2.user_id
I applied an index on the advert_views table and the users table as per below:
enter image description here
My query takes half an hour to execute. Is there a way how to improve my query speed?
Thanks!
Chris
First of all: Why do you outer join the tables? Or better: Why do you try to outer join the tables? A left join is meant to get data from a table even when there is no match. But then your results could contain rows with all values null. (That doesn't happen though, because adviews.user_id <> adviews2.user_id in your where clause dismisses all outer-joined rows.) Don't give the DBMS more work to do than necessary. If you want inner joins, then don't outer join. (Though the difference in execution time won't be huge.)
Next: You select from banned_users, but you only use it to check existence. You shouldn't do this. Use an EXISTS or IN clause instead. (This is mainly for readability and in order not to produce duplicate results. This probably won't speed things up.)
SELECT av1.user_id AS 'banned user_id',
av2.client_ip AS 'IPs used by banned users',
av2.user_id AS 'banned users that opened a new account'
FROM adviews av1
JOIN adviews av2 ON av2.client_ip = av1.client_ip AND av2.user_id <> av1.user_id
WHERE av1.user_id IN
(
SELECT user_id
FROM users
WHERE email_address IN (select email_address from banned_users where rec_status = 1)
)
GROUP BY av2.user_id;
You may replace the inner IN clause with a join. It's mostly a matter of personal preference, but it is also that in the past MySQL sometimes didn't perform well on IN clauses, so many people made it a habit to join instead.
WHERE av1.user_id IN
(
SELECT u.user_id
FROM users u
JOIN banned_users bu ON bu.email_address = u.email_address
WHERE bu.rec_status = 1
)
At last consider removing the GROUP BY clause. It reduces your results to one row per reusing user_id, showing one of its related banned user_ids (arbitrarily chosen in case there is more than one). I don't know your tables. Are you getting many records per reusing user_id? If not, remove the clause.
As to indexes I suggest:
banned_users(rec_status, email_address)
users(email_address, user_id)
adviews(user_id, client_ip)
adviews(client_ip, user_id)
I'm using MySQL with phpmyadmin -- which I only started to use today. If y'all can help me with this query you will create some major happiness:
My objective: to identify which users of status "userEnabled" have >3 records from the goals table associated with them.
userID is a field that relates the tables.
TABLE NAMES: users, goals
I think this would be the beginning of the query:
SELECT * FROM `users` WHERE `userEnabled`=1
Please let me know any details needed.
You need to join record from goals where the userid matches. Filter on the enabled flag, then count the results. Something like:
select * from users
INNER JOIN goals ON users.userID = goals.userID
WHERE user.userEnabled = 1
GROUP BY user.userID
HAVING count(user.userID) > 3
i read many join questions here but unable to understand and create my own to get the right result i want.
i have three tables for now that is status,members,friends friends table have two columns friend_id and member_id
all three tables have member_id common primary id of members table
now i want to get all the status created by members and member's friends
if i have three members with id's 1,2,3
friends table have id's 1,2 so these two becomes friends of each other
2 have 5 status updates and 1 have 2 status and 3 have 1 updates in status table
if i query against member 2 it should return 7 record...( 5 for 2 and 2 for 1 ) and should not return record of member 3.
if i query against member 1 it should return same record as for point 5.
do i need change in my tables structure ? please help how to get the record the way i want
How about a pre-query to the friends table for any qualifying member PLUS the member itself, then back-join to the rest of the tables...
select STRAIGHT_JOIN
PeopleList.Member_id,
members.last_name,
members.first_name, (etc with any other fields)
ms.status_id,
ms.description (etc with any other fields from member_status table)
from
( Select DISTINCT m.member_id
from Members m
where m.member_id = MemberDesiredVariable
union select f.friend_id AS member_id
from Friends f
where f.member_id = MemberDesiredVariable
union select f2.member_id
from Friends f2
where f2.friend_id = MemberDesiredVariable ) PeopleList
join members
on PeopleList.member_id = members.member_id
join member_status ms
on PeopleList.member_id = ms.member_id
This should get the primary person in question regardless of the person having ANY records in the "friends" table, such as a new person with no entries yet... they would at least qualify themselves and join to the members and member_status tables.
Then, in your scenario where member 1 is the criteria, it will query against the friends for any "Friend_IDs", and thus DISTINCT will have the 1 (direct from members) and the 2 where the member_id = 1, finds the Friend_id = 2. So now, this pre-query has two IDs and proceeds to get whatever the rest of your details you want.
The THIRD scenario is you want member 2... So, direct query to the members table guarantees their ID in the list to process, yet since their ID is NOT as a "MEMBER_ID" in the friends table, it has to look for itself as a "FRIEND_ID" from someone else and grab THAT Member's ID. So now, member 2 will also find member 1 and proceed to get details out.
As for member 3, if you queried against the Friends table, you'd get NO records at all, even IF the member 3 had some status records... It must be qualified against itself to be inclusive of the rest for processing... Yet will not find itself as a "member_id" nor "friend_id" in the friends table.
I couldn't actually test this at my current location, but logically should go no problem.
Finally, if you want the friends names REGARDLESS of having any "status" changes, change the last join to member_status to a LEFT JOIN.
--- Comment feedback
I can't suggest any books specifically, it just comes from years of experience...
1. UNDERSTAND THE RELATIONSHIP OF YOUR DATA...
2. Find out the inner-most "what do I want to get".
3. Throw all other elements out until you get the CRITERIA, not the CONTENT.
4. Keep your primary "get the criteria" up front... THEN Join in your other tables.
5. Then tack on all the other fields you want in the output result set
Trying to solve a complex query can very often be cluttered by all the OTHER elements of data a person is trying to get. Like so many other programming tasks... I like to make it work, then make it pretty. So too goes with querying. If your baseline query doesn't get the WHAT you want, it doesn't matter how many other tables you are joining together (left, outer, or normal join), your output will be wrong.
I've also added the clause "STRAIGHT_JOIN" to the sql at the top. This tells MySql to do the query in the order I've instructed it and don't have the optimizer try to think for me. This one clause has come in so frequently when joining a main table (such as millions of records) to "lookup" secondary tables that the query engine has falsely interpretted the lookup table as primary for querying which killed the performance...
Try to do some timed tests between the versions that work. If they are equally comparable, I would typically go with the one that I could understand in case I had to modify / change something in the future.
-- own records
SELECT member_id, friend_id, user_name, description
FROM
(SELECT M.member_id,
M.member_id friend_id,
M.user_name,
MS.description
FROM members M
LEFT JOIN member_status MS on MS.member_id = M.member_id
UNION ALL
-- friends records
SELECT M.member_id,
F.friend_id,
MF.user_name,
MS.description
FROM members M
JOIN ( SELECT friend_id member_id, member_id friend_id from friends
UNION SELECT member_id, friend_id from friends) F
ON F.member_id = M.member_id
LEFT JOIN member_status MS on MS.member_id = F.friend_id
LEFT JOIN members MF on MF.member_id = F.friend_id) R
WHERE R.member_id = 1
Here is the solution using UNION clauses. If the result if each SELECT is short (let's say less than 1000 rows) then it is faster than LEFT JOIN combined with a OR.
If by "friends of each other" you mean that you want :
(a) the status of the members marked as friend
+
(b) the status of the members which the considered member is marked as friend
then you should use the tree UNION below.
If you want only (a) then delete the last UNION.
SELECT s.status_id
FROM member_status AS s
WHERE (s.member_id=#id)
UNION ALL
SELECT s.status_id
FROM member_status AS s
INNER JOIN friends AS f ON (s.member_id=f.friend_id)
WHERE (f.member_id=#id)
UNION ALL
SELECT s.status_id
FROM member_status AS s
INNER JOIN friends AS f ON (s.member_id=f.member_id)
WHERE (f.friend_id=#id)
Before delving into the issue, first I will explain the situation. I have two tables such as the following:
USERS TABLE
user_id
username
firstName
lastName
GROUPS TABLE
user_id
group_id
I want to retrieve all users who's first name is LIKE '%foo%' and who is a part of a group with group_id = 'givengid'
So, the query would like something like this:
SELECT user_id FROM users WHERE firstName LIKE '%foo'"
I can make a user defined sql function such as ismember(user_id, group_id) that will return 1 if the user is a part of the group and 0 if they are not and this to the WHERE clause in the aforementioned select statement. However, this means that for every user who's first name matches the criteria, another query has to be run through thousands of other records to find a potential match for a group entry.
The users and groups table will each have several hundred thousand records. Is it more conventional to use the user defined function approach or run a query using the UNION statement? If the UNION approach is best, what would the query with the union statement look like?
Of course, I will run benchmarks but I just want to get some perspective on the possible range of solutions for this situation and what is generally most effective/efficient.
You should use a JOIN to get users matching your two criteria.
SELECT
user_id
FROM
users
INNER JOIN
groups
ON groups.user_id = users.users_id
AND groups.group_id = given_id
WHERE
firstName LIKE '%foo'
You don't need to use either a UNION or a user-defined function here; instead, you can use a JOIN (which lets you join one table to another one based on a set of equivalent columns):
SELECT u.user_id
FROM users AS u
JOIN groups AS g
ON g.user_id = u.user_id
WHERE g.group_id = 'givengid'
AND u.firstName LIKE '%foo'
What this query does is join rows in the groups table to rows in the users table when the user_id is the same (so if you were to use SELECT *, you would end up with a long row containing the user data and the group data for that user). If multiple groups rows exist for the user, multiple rows will be retrieved before being filtered by the WHERE clause.
Use a join:
SELECT DISTINCT user_id
FROM users
INNER JOIN groups ON groups.user_id = users.user_id
WHERE users.firstName LIKE '%foo'
AND groups.group_id = '23'
The DISTINCT makes sure you don't have duplicate user IDs in the result.