Mysql - Join matches and non-matches - php

This is related to my other question:
Managing Foreign Keys
I am trying to join the table of matches and non-matches.
So I have a list of interests, a list of users, and a list of user interests.
I want the query to return all interests, whether the user has the interest or not (should be null in that case), only where the user = x. Every time I get the query working its only matching interests that the user specifically has, instead of all interests whether they have it or not.

You should rather use LEFT JOINS
Something like
SELECT *
FROM interests i LEFT JOIN
userinterests ui ON i.interestID = ui.interestID LEFT JOIN
users u ON ui.userID = u.uiserID
WHERE userID = ?
where is the user id you are looking for.

SELECT *
FROM interests i
LEFT JOIN userinterests ui ON i.interestID = ui.interestID
LEFT JOIN users u ON ui.userID = u.uiserID
and u.userID = ?
If you put a where condition on a table that should have no records inteh main tbale, you convert the join from a left join to an inner join. The only time you should ever have a condition inthe where clasue for something one the right side of a left join is when you are searching for records that don't match (where u.userid is null, for instance)
Of course you should define the fields to be selected and never use select * in production code especially not when you have a join as it sends repeated information across the network (the data inteh join feilds is repeated) and is a waste of resources and poor prgramming practice for multiple reasons.

Related

Limit LEFT JOIN results to 1 with flexible where clause

my query looks like that:
SELECT
count(users.id)
FROM users
LEFT JOIN mail_sender_jobs_actions ON mail_sender_jobs_actions.userID = users.id
LEFT JOIN table2 ON table2.userID = users.id
LEFT JOIN table3 ON table3.userID = users.id
WHERE {$flexibleWhereClause}
Now, the mail_sender_jobs_actions table CAN (doesnt need to return anything) return multiple entries. I dont want to group the results but still limit the returns of mail_sender_jobs_actions to 1 so I dont get duplicates... Otherwise the count wouldnt work properly.
Scraped the whole web and found nothing working for me as I want to keep the where clause flexible. Any solution?
EDIT
so to explain the situation. We have a table with users (users). We have a table with actions (mail_seder_jobs_actions). We have other tables related to that query which are not relevant (table1, table2, table3)
If a user does an action, an entry is being created in the actions table.
The where clause is flexible, meaning it is possible that somebody wants to only show users with a specific action.
It is also possible that an action is not relevant to the user, so this entry gets ignored.
With where criteria you have there is no point using left join, since the where criteria applies to the table on the right hand side, effectively turning the left join into an inner join.
Apparently yo do not use any columns from the right hand side table, so instead of using joins, I would use an exists subquery.
SELECT
1 as count,
users.email
FROM users
WHERE EXISTS (SELECT 1
FROM mail_sender_jobs_actions
WHERE mail_sender_jobs_actions.userID = users.id
AND mail_sender_jobs_actions.type = '1'
AND mail_sender_jobs_actions.jobID = '106'
AND {$flexibleWhereClause})
However, there is little point in having the count() because it will always return 1. If you want to count how many records each user has in the mail_sender_jobs_actions table, then you have to use left join, group by, and move the where criteria into the join condition:
SELECT
count(mail_sender_jobs_actions.userID),
users.email
FROM users
LEFT JOIN mail_sender_jobs_actions ON mail_sender_jobs_actions.userID = users.id
AND mail_sender_jobs_actions.type = '1'
AND mail_sender_jobs_actions.jobID = '106'
AND {$flexibleWhereClause}
GROUP BY users.email

MySQL efficient SELECT query

I'm not a database professional, but currently working on one query (PHP->MySQL):
I have 3 tables:
'Items': id, name, link
'ItemsToUsers': id, item_id, user_id
'Users': id, email
Each 'Item' availability is submitted to regular changes which I check on fly by some algorithm.
My goal is to
1) SELECT all Items and check on fly if they are available
2) If Item is available, notify users who are monitoring it by email. For that I need to SELECT users from 'ItemsToUsers' and then get their emails from Users table.
I know how to do it in a straightforward way, but I feel that I will fall into running to many queries. (individual SELECT for every user...)
Is there a way to do it more efficiently: in one query or by changing the algorithm?
Thank you for your time!
There's not enough information to determine how an item is available. This severely impedes the ability to query item 2.
That said, let's suppose we add a "available" column to the Items table that is a tinyint of 0 for not available, 1 for available.
A query, then, which would get all email addresses for persons watching items that are available is:
SELECT u.email FROM Users AS u JOIN ItemsToUsers AS k ON k.user_id = u.id JOIN Items AS i on i.id = k.item_id WHERE i.available = 1;
Alternatively, you could use a subquery and IN.
Let's suppose you have a different table called Availability with the columns id, item_id and available, which again is a tinyint containing a 1 for available and 0 for not available.
SELECT u.email FROM Users AS u JOIN ItemsToUsers AS k ON k.user_id = u.id WHERE k.item_id IN (SELECT a.item_id FROM Availability AS a WHERE a.available = 1);
Again, without an idea of how you are getting a list of available products, it is impossible to optimize your queries for retrieving a list of email addresses.
Your steps allude to doing this in n+1 queries (where n = number of entries in the Items table):
SELECT * FROM Items; -- This is the +1 part
While iterating over that result set, you intend to determine if it's available and, if it is, to notify users who are watching it. Assuming you have a given item id and you want to select all users' email if that product id is active, then you could do this:
SELECT email FROM Users u
INNER JOIN ItemsToUsers iu ON iu.user_id = u.id
INNER JOIN Items i ON iu.item_id = i.id
WHERE i.id = {your item id}
You would be running this query for every item in your table. This is the n part.
In general you could instead generate a list of emails for all users who are watching all products that are active, after you have already determined which ones should be active:
SELECT DISTINCT email FROM Users u
INNER JOIN ItemsToUsers iu ON iu.user_id = u.id
INNER JOIN Items i ON iu.item_id = i.id
WHERE i.is_active = 1
This will get the job done in a total of 2 queries, regardless of how many users or items you have. As a bonus, this one can give you distinct emails, whereas the first solution would still need application-level code to remove duplicates returned by the multiple queries.
SELECT Items.id, Items.name, Items.link FROM Items
INNER JOIN ItemsToUsers ON ItemsToUsers.item_id = Items.items.id
INNER JOIN Users ON ItemsToUsers.user_id = Users.id ;

Possible to make MySQL ignore joins under certain conditions?

The following code successfully gives me details of logged in peoples' requested books, and the books themselves, from a specific library.
SELECT bookRequest.version, bookRequest.status, users.user_id, users.firstname, users.lastname, books.Author, books.Title
FROM users
INNER JOIN bookRequest
ON users.user_id=bookRequest.user_id
INNER JOIN books
ON bookRequest.bookID = books.ID
INNER JOIN libraryTokens
ON bookRequest.libraryID = libraryTokens.libraryID
WHERE libraryTokens.libraryID='". $libraryID ."'
The bookRequest table also has columns for directly storing manually added requests for books not in the system, requested by people not logged in. In this case, bookRequest.user_id is always -1, and the extra columns bookRequest.firstname, bookRequest.lastname, bookRequest.Author, and bookRequest.Title hold the guest's name and their non-listed book request's details.
I'm trying to figure out a way (I have no idea if it's even possible) of, when retrieving a library's book requests, ignoring the
INNER JOIN bookRequest
ON users.user_id=bookRequest.user_id
INNER JOIN books
ON bookRequest.bookID = books.ID
part of the query if bookRequest.user_id is -1, and instead retrieving bookRequest.firstname, bookRequest.lastname, bookRequest.Author, and bookRequest.Title, but not ignoring the joins if bookRequest.user_id>-1?
Is it possible, or wayyyyy too messy, and instead I should do two separate queries, and combine the resulting arrays using php?
Thanks for taking a look.
You'll need to change your JOIN to an OUTER JOIN and add a CASE statement to your query. Something like this:
SELECT bookRequest.version,
bookRequest.status,
CASE WHEN bookRequest.user_id = -1 THEN bookRequest.user_id ELSE users.user_id END userid,
CASE WHEN bookRequest.user_id = -1 THEN bookRequest.firstname ELSE users.firstname END firstname,
...
FROM bookRequest
JOIN libraryTokens
ON bookRequest.libraryID = libraryTokens.libraryID
LEFT JOIN books
ON bookRequest.bookID = books.ID
LEFT JOIN users
ON users.user_id=bookRequest.user_id
WHERE libraryTokens.libraryID='". $libraryID ."'

How can I join 3 tables with mysql & php?

I have a page that pulls the users Post,username,xbc/xlk tags etc which is perfect... BUT since I am pulling information from a MyBB bulletin board system, its quite different. When replying, people are are allowed to change the "Thread Subject" by simplying replying and changing it.
I dont want it to SHOW the changed subject title, just the original title of all posts in that thread.
By default it repies with "RE:thread title". They can easily edit this and it will show up in the "Subject" cell & people wont know which thread it was posted in because they changed their thread to when replying to the post.
So I just want to keep the orginial thread title when they are replying.
Make sense~??
Tables:mybb_users
Fields:uid,username
Tables:mybb_userfields
Fields:ufid
Tables:mybb_posts
Fields:pid,tid,replyto,subject,ufid,username,uid,message
Tables:mybb_threads
Fields:tid,fid,subject,uid,username,lastpost,lastposter,lastposteruid
I haev tried multiple queries with no success:
$result = mysql_query("
SELECT * FROM mybb_users
LEFT JOIN (mybb_posts, mybb_userfields, mybb_threads)
ON (
mybb_userfields.ufid=mybb_posts.uid
AND mybb_threads.tid=mybb_posts.tid
AND mybb_users.uid=mybb_userfields.ufid
)
WHERE mybb_posts.fid=42");
$result = mysql_query("
SELECT * FROM mybb_users
LEFT JOIN (mybb_posts, mybb_userfields, mybb_threads)
ON (
mybb_userfields.ufid=mybb_posts.uid
AND mybb_threads.tid=mybb_posts.tid
AND mybb_users.uid=mybb_posts.uid
)
WHERE mybb_threads.fid=42");
$result = mysql_query("
SELECT * FROM mybb_posts
LEFT JOIN (mybb_userfields, mybb_threads)
ON (
mybb_userfields.ufid=mybb_posts.uid
AND mybb_threads.tid=mybb_posts.tid
)
WHERE mybb_posts.fid=42");
Your syntax isn't appropriate for carrying out multiple LEFT JOINs. Each join needs its own ON clause.
SELECT
*
FROM
mybb_users
LEFT JOIN mybb_userfields ON mybb_users.uid = mybb_userfields.ufid
LEFT JOIN mybb_posts ON mybb_userfields.ufid = mybb_posts.uid
LEFT JOIN mybb_threads ON mybb_posts.tid = mybb_threads.tid
WHERE
mybb_posts.fid = 42
This query should give the results you want. But it may not be the most efficient query for getting those results. Check the output of EXPLAIN as part of testing, to make sure it is not using table scans or anything like that.
Do all of these joins need to be LEFT JOINs? LEFT JOIN forces MySQL to join the tables in the indicated order, rather than allowing the query optimiser to determine the best order in which to join them. That's why you might need to be careful about the query execution plan. The main difference between JOIN and LEFT JOIN as far as query output is concerned is that LEFT JOIN resultsets will contain at least one row for each row of the table on the left-hand side of the join, whereas a regular JOIN will not contain a row if there aren't matches on the right-hand side of the join.
Edit: Also, you say that "I don't want it to SHOW the changed subject title, just the original title of all posts in that thread." This suggests that you only want a subset of the columns from these tables, in which case SELECT * is inappropriate.

User/Subscriber sql query

I'm creating a table that shows all the registered users to which the current user has not yet subscribed to. But once he has subscribed someone, I need to filter that list to exclude those.
Let's say the theres a table called subscribed which lists the User and to whom he is subscribed to.
|UserId||SubscriberID|
Its easy to make it into multiple queries, but I've been unsuccessfully trying to make it into one query, to save an extra loop of MySQL calls.
Here's What I have so far:
SELECT u.UserID, FullName, UserName from users u
LEFT JOIN subscribed t ON
((u.UserName LIKE '%$search%' OR
u.Email LIKE '%$search%') AND
({$_SESSION['ID']} = t.UserID
AND t.FollowerID != u.UserID)
)
I know the last part of the query is wrong, since I only compare if the UserID and the FollowerID don't match for one particular row, not the entire table.
To find a list of results in table A that are not in table B, you have two options. Either use the NOT IN syntax, or LEFT JOIN and look where the PK field in the B table is NULL.
NOT IN example:
SELECT a.id FROM a WHERE a.id NOT IN (SELECT b.id FROM b)
LEFT JOIN example:
SELECT a.id
FROM a
LEFT JOIN b ON (a.id = b.id)
WHERE (b.id IS NULL)

Categories