Build MySql Query - mysql

I have a database table called private_message. I run the following query on it to get a result of all the user id's that one user (47762) sent a private message to:
SELECT `to_id` FROM `private_message` WHERE `from_id` = 47762
I now want to use this query or some other way on another table called users to get the email address of all the users who received the email address from 47762.
I tried the following:
SELECT * FROM `users` WHERE `sid` = (SELECT `to_id` FROM `private_message` WHERE `from_id` = 47762)
In the above query, users.sid is the same as private_messages.to_id
I got the error #1242 - Subquery returns more than 1 row.
What I want is to get the email addresses of users that were sent a private message from user 47762 but the users table.
I'm a novice with MYSQL so would appreciate some help here.
Thanks.

You should use IN instead od = since you can't compate (=) sid to multiple values.
SELECT * FROM `users` WHERE `sid` IN (SELECT `to_id` FROM `private_message` WHERE `from_id` = 47762)
Anyway you can do a JOIN here instead:
SELECT u.* FROM `users` AS u
JOIN `private_message` AS pm ON u.`sid` = pm.`to_id`
WHERE pm.`from_id` = 47762
It will be more efficient in most cases.

Related

query with several LEFT JOIN , not getting desired result

Here's a sql fiddle
Can anyone tell me how can I get this output using LEFT JOIN?
notification_recipient pm_sender msg modification_page_id
Peter Tom Hello NULL
notification_recipient pm_sender msg modification_page_id
Peter NULL NULL 2
Here's the query that I have tried:
SELECT u.name AS notification_recipient,us.name AS pm_sender,
p.msg,um.page_id AS modification_page_id
FROM notification n
LEFT JOIN pm p ON p.pm_id = n.pm_id
LEFT JOIN users u ON u.user_id = p.recipent_id
LEFT JOIN users us ON us.user_id = p.sender_id
LEFT JOIN user_modification um ON um.modification_id = n.modification_id
WHERE u.name = 'Peter'
AND n.is_read = '0'
I was looking for some sort conditional join, which means joining different tables based on whether a value exists in a field, but I couldn't find one that would work on my example. Any other efficient solution would also be appreciated.
Background:
I'm planning to make a notification system that sends out different types of messages to users (private messages among users, message that their modifications to an entry has been approved etc).
When a user logs in, I want to make a query to find out if there's any unread notifications for that user. If yes, a notification will be sent to him via Ajax.
To illustrate, suppose that Tom has sent a private message to Peter and his modification to an entry is approved, two triggers from table pm and user_modification will be invoked to add two new rows into notification. Column pm_id is referenced by pm and modification_id is by modification. is_read is defaulted 0 as Not Read.
Here's the table schema:
CREATE TABLE notification
(`id` int, `modification_id` int,`pm_id` int,`is_read` int)
;
INSERT INTO notification
(`id`,`modification_id`,`pm_id`,`is_read`)
VALUES
(1,1,NULL,0),
(2,NULL,1,0),
(3,2,NULL,0)
;
CREATE TABLE user_modification
(`modification_id` int, `user_id` int,`page_id` int, `is_approved` int)
;
INSERT INTO user_modification
(`modification_id`,`user_id`,`page_id`,`is_approved`)
VALUES
(1,1,5,1),
(2,2,2,1),
(3,3,3,0)
;
CREATE TABLE pm
(`pm_id` int, `sender_id` int,`recipent_id` int,`msg` varchar(200))
;
INSERT INTO pm
(`pm_id`,`sender_id`,`recipent_id`,`msg`)
VALUES
(1,1,2,'Hello');
CREATE TABLE users
(`user_id` int, `name`varchar(20))
;
INSERT INTO users
(`user_id`,`name`)
VALUES
(1,'Tom'),
(2,'Peter'),
(3,'David')
;
Here's the output I want for a notification if user David logs in. Each row for each message type.
notification_recipient pm_sender msg modification_page_id
Peter Tom Hello NULL
notification_recipient pm_sender msg modification_page_id
Peter NULL NULL 2
The notification to Peter will be like this:
`1.You have received a private message from Tom.
2.your modification on <a href='mysite.com/5'>that page</a> is approved`.
This query should do the job. Here's SQLFiddle
SELECT
n.id,
IF(pmu.name IS NULL, pmm.name, pmu.name) recipient,
pmus.name sender, pm.msg, m.modification_id
FROM
notification n
LEFT JOIN user_modification m ON (n.modification_id = m.modification_id)
LEFT JOIN pm ON (n.pm_id = pm.pm_id)
LEFT JOIN users pmu ON (pm.recipent_id = pmu.user_id)
LEFT JOIN users pmus ON (pm.sender_id = pmus.user_id)
LEFT JOIN users pmm ON (m.user_id = pmm.user_id)
WHERE
(pmu.name = 'Peter' OR
pmm.name = 'Peter') AND
n.is_read = 0;

UPDATE using SELECT as WHERE

I want to run an Update where the WHERE statement consists of 2 SELECTS, is this at all close to how you do that?
UPDATE Requests SET Response=1 WHERE
sender=SELECT userID FROM Users WHERE Username=?) and
Reciever = SELECT userID FROM Users WHERE Username=?
Thank!
You're missing parentheses, that's all.
UPDATE Requests SET Response=1 WHERE
sender = (SELECT userID FROM Users WHERE Username=?) and
receiver = (SELECT userID FROM Users WHERE Username=?);

How to use data in same table on UPDATE query?

update accounts set password=(select password from accounts where name='joongsu')
where id=(select accountid from characters where name='Nobless')
it doesn't work with error message "You can't specify target table 'accounts' for update in FROM clause"
Why doesn't it work? select queries in above only return 1 row.
Perhaps you should try this one:
UPDATE accounts
SET accounts.password =
(
SELECT something.password
FROM (SELECT * FROM accounts) AS something
WHERE something.name='joongsu'
)
WHERE accounts.id=(SELECT accountid FROM characters WHERE name='Nobless');
It's a hack, but I tested it and it works on my test data. For some reason MySQL doesn't allow using the same table in inner queries as the one being updated.
UPDATE
accounts AS account_to_be_updated
JOIN
characters
ON characters.accountid = account_to_be_updated.id
AND characters.name = 'Nobless'
CROSS JOIN
( SELECT password
FROM accounts
WHERE name = 'joongsu'
) AS existing_account
SET
account_to_be_updated.password = existing_account.password ;
Is this what you looking out for?
;with CTE as
(
select password from accounts where name='joongsu' limit 1
)
update accounts set password= CTE.password
where id in
(select accountid from characters where name='Nobless')

Select where null only if a non-null value doesn't exist?

I have a simple user preferences table that looks like this:
id | user_id | preference_name | preference_value
What makes this table unique though is if the user_id field is null, it means it is the default value for that preference. I'm trying to get all the preferences for a user and use the default value only if an actual value hasn't been specified for that user.
So basically I need to:
SELECT * FROM user_preferences WHERE user_id = {userIdVar} OR user_id IS NULL;
BUT, I want to throw out a user_id is null result if there is another row in the result set with the same preference_name and a value for user_id.
Is there a way to do this with a single SQL query or should I do this in code?
Use NOT EXISTS:
SELECT up1.*
FROM user_preferences up1
WHERE ( NOT EXISTS(SELECT 1
FROM user_preferences up2
WHERE user_id = {userIdVar})
AND user_id IS NULL )
OR ( user_id = {userIdVar} );
There are various ways you can do this, but if all preferences have a default value, or you have a complete list of preferences somewhere else, I would do it like this:
select
default_preferences.preference_name,
coalesce(
real_user_preferences.preference_value,
default_preferences.preference_value) as preference_value
from
(select * from user_preferences where user_id is null)
as default_preferences
left join
(select * from user_preferences where user_id = #user_id)
as real_user_preferences
on
real_user_preferences.preference_name = default_preferences.preference_name
You've tagged your question both MySQL and SQL Server, I don't know which dialect you're looking for. I know SQL Server accepts this syntax, but it might need some tweaking for MySQL.
Edit: funkwurm points out that subqueries make this likely to perform poorly on MySQL. If that turns out to be a problem, it can be rewritten without subqueries as
select
default_preferences.preference_name,
coalesce(
real_user_preferences.preference_value,
default_preferences.preference_value) as preference_value
from
user_preferences as default_preferences
left join
user_preferences as real_user_preferences
on
real_user_preferences.preference_name = default_preferences.preference_name
and real_user_preferences.user_id = #user_id
where
default_preferences.user_id is null
Edit 2: if there are preferences that do not have a default value, the first version can be modified to use full join instead of left join, and take preference_name from either the defaults or the user-specific preferences, just like preference_value. However, the second version is not so easily modified.
COALESCE returns the first non null values of the params provided: http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_coalesce
So if you grab the set of default preferences and JOIN them with the users preferences, you can use the COALESCE in your columns to populate the correct values.
This should work to select the first row that is either NULL or set the the user_id variable where the user_id variable is preffered if both are set and then shows every preference_name only once.
SELECT
*
FROM
(
SELECT
*
FROM
user_preferences
WHERE
user_id = {userIdVar} OR
user_id IS NULL
ORDER BY
CASE WHEN user_id IS NULL THEN 1 ELSE 0 END
) sub_query
GROUP BY
preference_name
SQL FIDDLE

Is it possible to combine MySQL queries to multiple tables into a single query based on the results from one of the queries?

The $userid of the currently logged in user is all the is currently available in the PHP code. I want to run a query against the mysql tables to return all of the status updates for myself and for friends ordered by createddate DESC.
MySQL Sample database tables:
[statusupdates]
statusupdateid int(8)
ownerid int(8)
message varchar(250)
createddate datetime
[friends]
friendid int(8)
requestfrom int(8)
requestto int(8)
daterequested datetime
dateupdated datetime
status varchar(1)
Question: Can I perform a single string query that returns each statusupdates.userid and the statusupdates.message ordered by statusupdates.createddate DESC?
Or do I have to run a query for each friends record where the $userid is in either the friends.requestfrom or friends.requestto then, run another query for alternate friends.requestfrom or friends.requestto (the one that doesn't include $userid), then sort all of the results by statusupdate.createddate and then get the statusupdates.message?
You want to look at MySQL Joins.
I think this may do something like what you're after, but it will almost definitely need debugging!
SELECT DISTINCT s.ownerid, s.message
FROM statusupdates s
LEFT JOIN friends f ON ($userid = f.requestfrom)
LEFT JOIN friends f ON ($userid = f.requestto)
ORDER BY s.createddate;
This is untested, but should work or at least get you in the right direction.
You could use IN() where you get a list of userids from a sub query. That subquery does a UNION on 2 queries - 1st to get the requestfrom userids, and 2nd to get the requestto userids. Finally we add an OR to include the current userid.
also, I assume that you also want to filter out where status = 1, as you don't want updates from those who have not confirmed friendships
SELECT s.ownerid, s.message
FROM statusupdates s
WHERE s.ownerid IN (
SELECT f1.requestfrom
FROM friends f1
WHERE f1.requestto = $userid
AND f1.status = 1
UNION
SELECT f2.requestto
FROM friends f2
WHERE f2.requestfrom = $userid
AND f2.status = 1
)
OR s.ownerid = $userid
ORDER BY s.createddate DESC
take a look at this sqlFiddle example - http://sqlfiddle.com/#!2/85ea0/3