SQL NOT query returns wrong results - php

I have 2 SQL tables. users contains users and registrants contains all the registrations done for an event. Given that I have the id of the event - I am successfully printing out those users that are registered for a specific event. However I want to print all the users that are NOT registered for the event and here I fail at producing the correct result. My query is as follows:
SELECT users.fName, users.lName, users.uid
FROM users, registrants
WHERE registrants.tourId = '$id' AND NOT registrants.pId = users.uid
The result of this is an array of 33 users printed out, although I have only 12, 3 of which are already registered for the event. Is the SELECT command wrong or I will need to dig into the php code, although I am pretty sure all the work there is correct.

By specifying no JOIN syntax, but instead specifying a kind of inequality join in the WHERE clause, you are effectively asking SQL to find all combinations of users and registrants, with the additional filter criteria in the WHERE clause, hence the large number of rows.
I believe the query you are looking for is more like this:
SELECT users.fName, users.lName, users.uid
FROM users
WHERE users.uid NOT IN (
SELECT registrants.pId
FROM registrants
WHERE registrants.tourId = '$id');
"Find all users, except those registered for tourId = '$id'"

Assuming that "registrants.pId" is the foreign key to the table "users", this request will give you every combination of registrants and users where the registrant and user are not related.
The way I see you could achieve the intended result is to select users using a subquery to exclude those who are regitered to the event. Which would look something like :
$query = "SELECT users.fName, users.lName, users.uid FROM users WHERE users.uid not in (SELECT users.uid FROM users, registrants WHERE registrants.tourId = '$id' AND registrants.pId = users.uid)"
there might be a simpler way to do this, though I do not see it right now.

Have you tried ".... AND registrants.pld <> users.uid"; ?
or
".... AND registrants.pld != users.uid";

You need to use an anti-join, that is, an outer join where you want to return from the dependent side of the join values where the independent side of the join is null.
In your case that looks like this:
SELECT users.fName, users.lName, users.uid
FROM users
LEFT OUTER JOIN registrants
ON users.uid = registrants.pId
AND registrants.tourId = '$id'
WHERE registrants.pId IS NULL

Related

Updating a row based on number of existing records

I'm trying to update a number of rows in a user table based on a value occurring more than once. In this case it's user email - as the user can sign up to multiple websites hosted in this application.
UPDATE users SET email = REPLACE(email,'#', CONCAT('+',user_id,'#'))
WHERE user_id IN (
SELECT user_id FROM users HAVING COUNT('email') > 1
);
This query gives me the following error;
ERROR 1093 (HY000): You can't specify target table 'customer_entity' for update in FROM clause
I've tried a number of variations but none of these seem to work.
MySQL does not support this syntax. Instead, you can self-join an aggregate query:
UPDATE users u
INNER JOIN (SELECT user_id FROM users GROUP BY user_id HAVING count(email) > 1) u1
ON u1.user_id = u.user_id
SET u.email = REPLACE(e.email,'#', CONCAT('+', u.user_id, '#'))
The two links I cited have lots of great suggestions, and GMB's suggestion sounds promising, too.
Q: Have you really looked at each of these (multiple different!) alternatives, and tried them out yourself, with your dataset? What happened?
SUGGESTION (taking GMB's example):
Verify the select works (returns one or more rows):
SELECT user_id FROM users GROUP BY user_id HAVING count(email) > 1)
Combine the "update" with the "join" (different syntax):
UPDATE users u1
SET u1.email = REPLACE(e.email,'#', CONCAT('+', u1.user_id, '#'))
INNER JOIN users u2
ON u1.user_id = u2.user_id
GROUP BY u2.user_id HAVING count(u2.email) > 1;
Please let us know the results.

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

PHP, MySQL: How to get the user's first and last name

I got stucked with this query in the past couple of hours, and I badly need someone to help me figure it out. I'm try to finish my private message system, but I lost myself in the database tables. I've created three tables for the system, and they are as follows:
CONVERSATION(**conversation_id**, subject)
CONVERSATION_MEMBER(**conversation_id**, **user_id**, conversation_last_view, conversation_deleted)
CONVERSATION_MESSAGE(**message_id**, conversation_id, user_id, message_date, message_text)
Ok, so in my function to get all conversations of a specific user, I'm fetching the subject, the date, and at last I want to show the user who is he/she talking to. I wrote the following query:
SELECT
c.conversation_id,
c.conversation_subject,
MAX(cmes.message_date) AS conversation_last_reply,
FROM conversation c, conversation_member cmem, conversation_message cmes
WHERE c.conversation_id = cmes.conversation_id
AND c.conversation_id = cmem.conversation_id
AND cmem.user_id = {$_SESSION['user_id']}
AND cmem.conversation_deleted = 0
GROUP BY c.conversation_id
ORDER BY conversation_last_reply DESC
And, at last I'm tryin to get the user's first and last name (the other user in the conversation), but I lost myself on how to do that. I've tried to create another function that will get the conversation id, while looping through the results of the first query, and return the user's first and last name, but it didn't work out.
Btw, for the users I have another table... I guess I don't have to tell you. Ok, thank you.
$sql = "SELECT (user.forename, user.surname, other_fields...)
FROM conversation
INNER JOIN conversation_member
ON conversation.conversation_id = conversation_member.conversation_id
INNER JOIN conversation_message
ON conversation.conversation_id = conversation_message.conversation_id
INNER JOIN users_table /* replace this with the name of your user table */ AS user
ON user.user_id = conversation_member.user_id
WHERE user.user_id = :userid
AND conversation_member.conversation_deleted = 0
GROUP BY conversation.conversation_id;"
$query = $db->prepare($sql);
$query->bindParam(':userid', $userid, PDO::PARAM_INT);
$query->execute();
$results = $query->fetchAll();
$user = $results[0]["user"]; //stores array of user fields (forename, surname, etc)
SELECT
c.conversation_id,
c.conversation_subject,
user.firstname,
user.lastname,
MAX(cmes.message_date) AS conversation_last_reply,
FROM conversation c, conversation_member cmem, conversation_message cmes, user_tablename user
WHERE c.conversation_id = cmes.conversation_id
AND c.conversation_id = cmem.conversation_id
AND cmem.user_id = {$_SESSION['user_id']}
AND cmem.conversation_deleted = 0
AND user.user_id_column = whatever.you_used_as_foriegn_key
GROUP BY c.conversation_id
ORDER BY conversation_last_reply DESC
Assuming the columns names are like that
You need to join with the "conversation_member" table again, this time, selecting the other user's id where the same conversation_id and message_id applies:
SELECT
c.conversation_id,
c.conversation_subject,
MAX(cmes.message_date) AS conversation_last_reply,
cmem2.user_id AS other_user_id
CONCAT(usr_tbl.firstname, ' ', usr_tbl.lastname) AS other_user_name
FROM conversation c
JOIN conversation_member cmem
JOIN conversation_message cmes
JOIN conversation_member cmem2
JOIN users_table usr_tbl
ON c.conversation_id = cmes.conversation_id
AND c.conversation_id = cmem.conversation_id
AND cmem.user_id = {$_SESSION['user_id']}
AND cmem.conversation_deleted = 0
AND c.conversation_id = cmem2.conversation_id
AND cmem2.user_id = {$_SESSION['other_user_id']}
AND cmem2.conversation_deleted = 0
GROUP BY c.conversation_id
ORDER BY conversation_last_reply DESC
I don't see why you're having trouble getting the user's first and last name if you were able to construct the rest of that query by yourself?
Try something along the lines of, :
SELECT cmem.user_id, u.first_name, u.last_name, c.conversation_id, c.conversation_subject, MAX(cmes.message_date) AS conversation_last_reply
FROM conversation c
INNER JOIN conversation_member cmem on c.conversation_id=cmem.conversation_id
INNER JOIN conversation_message cmes on c.conversation_id=cmes.conversation_id
INNER JOIN users u on u.user_id = cmem.user_id
WHERE cmem.user_id = {$_SESSION['user_id']} AND cmem.conversation_deleted = 0
GROUP BY cmem.user_id, u.first_name, u.last_name, c.conversation_id, c.conversation_subject
Now, I think you should also be reconsidering your database structure so that all these joins are not necessary. I see several problems. One, your database seems to be over-normalized. Why do you have a separate "Conversations" table that has only two fields, conversation id and subject? In any message system I've ever seen, the subject is always visible so you would always have to join the conversation table just to get the subject field. The conversation_id is in every other table anyway. Just add the subject field to the message table and eliminate the conversation table if that's all it's holding, normalization isn't always a good thing.
Second, why do you set a flag for deleted messages instead of just deleting them? I've also never seen a message system that lets me restore messages I've deleted. At the very least, if you want to retain them for whatever reason you should move them to an archive table so that the primary table you're running selects off of doesn't have to deal with the performance hit of parsing through meaningless "deleted" entries.
Lastly, what is the conversation_member table anyway? Based on my interpretation, it's supposed to represent a member of the conversation since it has a user_id. Why would the conversation delete flag be present for a single member of a conversation? If anything it should be in the conversation table. With that improvement, the only field left in it is conversation_last_view, which really no one cares about. The more important thing is conversation_last_post, which can be easily derived from the timestamp of the last message posted in the thread.
Ultimately, if you just want to see the first and last names appended to your query it's as simple as joining the users table and displaying those two entries. The SQL query I provided should get you close if it doesn't work straight out, I'm too lazy to copy your database and try it myself. However, I think you should really consider the overall design of your database as well so you don't run into needless performance issues down the road.
To answer the question of: Finding all users in a conversation, MINUS the current user:
SELECT (users.forename, users.surname)
FROM conversation_members AS members
INNER JOIN users_table AS users
ON members.user_id = users.user_id
WHERE members.conversation_id = :conversationid
AND NOT users.user_id = :userid
Where :userid is the current user and :conversationid is the conversation in question.

SELECT statement inside another SELECT statement

I have two tables called messages and users. In the messages table, there's a field which is a foreign key to the users table that is basically the users ID. I am trying to retrieve results from the messages table using a SELECT query, but I want the users username rather than their user ID. This SQL is wrong but I think it gets across the idea of what I'm trying to do:
SELECT (SELECT username FROM `users` WHERE u_id=?), message, sent FROM `messages` WHERE r_id=? AND sent > ?
Basically, I want to use the users ID stored in the messages table to get the users username to be returned AND get results from the messages table in one query.
I think JOINs are the tool for this, but I have very little bar no SQL experience.
Thanks.
You want to JOIN the two tables together, using the common u_id column.
SELECT u.username, m.message, m.sent
FROM messages m
INNER JOIN users u
ON m.u_id = u.u_id
WHERE m.r_id = ?
AND m.sent > ?
This is the case for an inner join:
select
u.username,
m.message,
m.sent
from
messages m
inner join users u on
m.u_id = u.u_id
where
m.r_id = ?
and u.u_id = ?
and m.sent > ?
What you're doing here is taking the messages table and saying, "Okay, grab me everything in the users table where the u_id column from messages equals the u_id column from users.
The where clause then filters your results down based on the parameters you want to pass it.
You can join tables ad nauseum, so you don't have to do just one, for future reference.
If you'd like to read more about joins and the different types, I highly encourage you to read Atwood's post on it here.
You can join two tables like this too:
SELECT
u.username,
m.message,
m.sent
FROM
messages m,
users u
WHERE
m.r_id=? AND
m.sent > ? AND
m.u_id = u.u_id
m.u_id is user id in messages table
You can refactor the query slightly like this:
select
u.username,
m.message,
m.sent
from
(
select u_id,message,sent
from messages
where r_id = ?
and sent > ?
) m
inner join
(
select u_id,username
from users
where u_id = ?
) u using (u_id);
You need to make sure you have a compound index on r_id and sent
ALTER TABLE messages ADD INDEX (r_id,sent);
Your query needs only slight correction and then it's equivalent to a LEFT JOIN (or INNER JOIN, exactly as #Joe Stefanelli's answer if messages.u_id is never NULL):
SELECT
(SELECT username FROM `users` WHERE u_id = messages.u_id) AS username
, message
, sent
FROM messages
WHERE r_id = ?
AND sent > ?

cross referencing 2 tables and getting results

I have 2 mysql tables
one has a list of user ids that are associated with a city. ie "Fort Lauderdale" but the user id is actually in a column called entity_id and the city is in a field called field_city_value.
This query brings back all of the entity_ids in "Fort Lauderdale"
SELECT entity_id
FROM `field_data_field_city`
WHERE `field_city_value` LIKE 'Fort Lauderdale'
and then this query brings back the mail for the user id
SELECT mail
FROM `users`
WHERE `uid` =42
I want to combine the 2 and get all of the mails for all of the user ids that match Fort Lauderdale.
Use a join statement.
http://dev.mysql.com/doc/refman/5.0/en/join.html
Heres the long winded way of doing it.. Untested, no mysql access on this box.
SELECT field_data_field_city.entity_id,users.mail FROM users,field_data_field_city WHERE field_city_value LIKE 'Fort Lauderdale' AND field_data_field_city.entity_id = users.uid
Or
SELECT * FROM field_data_field_city city INNER JOIN users user on city.entity_id=user.uid
Can we assume that users.uid is the same as field_data_field_city.entity_id? If that's the case you'll want to look into using MySQL joins
You can try a subselect:
SELECT mail FROM users WHERE uid IN (SELECT entity_id FROM field_data_field_city WHERE field_city_value LIKE 'Fort Lauderdale')
This should do it. You might want to change it a little bit for duplicates or similar, but this simple query should work
SELECT u.mail
FROM users u
LEFT JOIN field_data_field_city fdfc ON fdfc.entity_id = u.uid
WHERE fdfc.field_city_value LIKE 'Fort Lauderdale'

Categories