Selecting with subqueries in MySQL (Subqueries with ANY, and IN) - mysql

Thanks for the great answers!
For More Information
More info on MySQL IN comparison operator
Joining Tables
Subqueries with ANY, IN, and SOME
This is hard to explain, so lets set the stage...
userActions userGroupMap
+------+--------+ +------+-------+
| user | action | | user | group |
+------+--------+ +------+-------+
| x | acted! | | x | a |
| y | acted! | | y | a |
| y | acted! | | z | b |
| z | acted! | +------+-------+
| y | acted! |
| z | acted! |
| x | acted! |
| z | acted! |
+------+--------+
I want to select group a's actions. My idea was to
SELECT actions, user FROM userActions
WHERE user = (SELECT user, group FROM userGroupMap WHERE group = a)
But obviously this subquery returns more than one row. Should I use a JOIN?
Subquery returns more than 1 row

One approach is this:
SELECT actions,
user
FROM userActions
WHERE user IN
(SELECT user
FROM userGroupMap
WHERE [group] = 'a'
);
However, with large tables, this query tends to be inefficient and doing a join is better:
SELECT actions,
userActions.user
FROM userActions
INNER JOIN
(SELECT user
FROM userGroupMap
WHERE [group] = 'a'
) AS tmp
ON userActions.user = tmp.user;
Alternatively, as Jonathon mentioned, you could have done this and its pretty much as efficient, if not more:
SELECT actions,
userActions.user
FROM userActions
INNER JOIN userGroupMap
ON userActions.user = userGroupMap.user
WHERE [group] = 'a';

SELECT actions, user FROM userActions
WHERE user IN (SELECT user FROM userGroupMap WHERE group = a)
SELECT actions, user FROM userActions
WHERE user = ANY (SELECT user FROM userGroupMap WHERE group = a)
(Amended: only the user column should be returned, as noted by others.)

Actually, this query will give you what you need:
SELECT actions, user
FROM userActions
WHERE user IN
(SELECT user FROM userGroupMap WHERE group = 'a')

Couldn't you just do something like:
SELECT
a.actions,
a.user
FROM
userActions a
INNER JOIN userGroupMap g
ON a.user = g.user
WHERE
g.group = 'a'

Rather use join than subquery:
SELECT
userActions.action,
userActions.user
FROM
userActions
CROSS JOIN userGroupMap ON
userGroupMap.user = userActions.user AND
userGroupMap.group = 'a'

SELECT actions, user FROM userActions
WHERE user = (SELECT user FROM userGroupMap WHERE group = a)
The subquery was returning user and group (two fields) when it should be returning just user.

Related

Select entries that aren't part of a pair in another table

Tables:
users friends
+-------+----+ +-----+-----+
| name | id | | id1 | id2 |
+-------+----+ +-----+-----+
| user1 | 1 | | 1 | 2 |
+-------+----+ +-----+-----+
| user2 + 2 |
+-------+----+
In my database id1 in the friends table is the dominant column, which means that if id1 = 1 and id2 = 2 then user1 is friends with user2 but not the other way around.
I'm trying to select all users from the users table that don't have X as id1 in friends. This is because I don't want userX to be able to find friends that he's already added.
Here is my failed attempt:
SELECT * FROM users LEFT JOIN friends ON users.id != friends.id2 WHERE friends.id1 = X AND users.id != X;
I added users.id != X since we don't want to return the user himself in a search for other users.
You can use an outer join to do that, you attempt went into the right direction:
select u.*
from users u
left join friends f
on u.id = f.id2
where f.id1 is null
An outer join returns NULL for every non-matched item at least.
using not in() (more efficient than not exists() in mysql)
select *
from users
where id != xi
and id not in (
select id.2
from friends
where friends.id1 = x
)
using not exists()
select *
from users
where id != x
and not exists (
select 1
from friends
where friends.id1 = x
and friends.id2 = users.id
)

UPDATE query SUM multiple selects

I need to update a field calculated by the sum of multiple selects. The selection part is working, but I can't find a way to update the user table
user
+------+---------+
| id | total |
+------+---------+
| 1 | |
| 2 | |
unita
+------+-------+-----+
| id | uid | num |
+------+-------+-----+
| 1 | 1 | 25 |
| 1 | 2 | 10 |
unitb
+------+-------+-----+
| id | uid | num |
+------+-------+-----+
| 9 | 1 | 225 |
| 9 | 2 | 10 |
class
+------+--------+------+
| id | name | cost |
+------+--------+------+
| 1 | class1 | 100 |
| 9 | class9 | 500 |
SELECT uid, SUM(score) FROM (
SELECT unita.uid, SUM(class.cost * unita.num) AS cost FROM unita, class WHERE unita.id = class.id GROUP BY unita.uid
UNION
SELECT unita.uid, SUM(class.cost * unitb.num) AS cost FROM unitb, class WHERE unitb.id = class.id GROUP BY unitb.uid
) x GROUP BY uid
The update command should sum all cost per user
User 1: (25*100)+(225*500) = 115000
User 2: (10*100)+(10*500) = 6000
It this possible within 1 SQL command. The unit tables are locked, so I can't modify anything
You can use join to bring in the results from your subquery:
UPDATE user u JOIN
(SELECT uid, SUM(score) as total
FROM (SELECT unita.uid, SUM(class.cost * unita.num) AS cost
FROM unita JOIN
class
ON unita.id = class.id
GROUP BY unita.uid
UNION ALL
SELECT unita.uid, SUM(class.cost * unitb.num) AS cost
FROM unitb JOIN
class
ON unitb.id = class.id
GROUP BY unitb.uid
) x
GROUP BY uid
) newvals
ON u.id = newvals.uid
SET u.total = newvals.total;
Three notes:
Note the use of UNION ALL instead of UNION. Not only does this improve performance because duplicates are not eliminated, but it also fixes a potential problem if both subqueries return the same value.
Note the use of proper join syntax. Simple rule: never use commas in the from clause.
This will not set the total to 0 if there is no match. If you desire this, change the join to a left join and the set to SET u.total = COALESCE(newvals.total, 0).
You can use the update-join syntax:
UPDATE `user` u
JOIN (SELECT uid, SUM(score) AS total
FROM (SELECT unita.uid, SUM(class.cost * unita.num) AS cost
FROM unita, class
WHERE unita.id = class.id
GROUP BY unita.uid
UNION ALL
SELECT unitb.uid, SUM(class.cost * unitb.num) AS cost
FROM unitb, class
WHERE unitb.id = class.id
GROUP BY unitb.uid) x
GROUP BY uid) s ON s.uid = u.id
SET u.total = s.total
Notes:
The inner query in the OP has a bug. Since it uses union instead of union all, if the same uid has the same total score in both units, it will only be counted once, instead of twice. The above query fixes this.
Implicit joins have been deprecated for ages. The above query still uses them to math the OP's style, but the use of explicit joins is highly recommended.
E.g.:
UPDATE `user` u
JOIN (SELECT uid, SUM(score) AS total
FROM (SELECT unita.uid, SUM(class.cost * unita.num) AS cost
FROM unita
JOIN class ON unita.id = class.id
GROUP BY unita.uid
UNION ALL
SELECT unitb.uid, SUM(class.cost * unitb.num) AS cost
FROM unitb
JOIN class ON unitb.id = class.id
GROUP BY unitb.uid) x
GROUP BY uid) s ON s.uid = u.id
SET u.total = s.total

How to count the number of people who choose a particular location?

i need help in select sql statement.
in my mysql database:
location table
serialID(AI)|locations | telephone | address
---------------------------------------------
1 | A
2 | B
3 | C
4 | D
users table
userID | location chosen
-------------------------
1 | A
2 | B
3 | B
I want to count the number of people who choose a particular location and display in the table. So if this particular location have more users choosen as their favourite location, it will move up to the first row. May I know how can I do this?
something like this when it populate into dynamic table ->
location | address | telephone | user's favourable
B | - | - | 2
A | - | - | 1
C | - | - | 0
D | - | - | 0
You could just do a query like this:
SELECT l.locations, l.telephone, l.address, COUNT (u.userID) as `location_count`
FROM location AS l
LEFT OUTER JOIN users AS u on l.locations = u.location_chosen
GROUP BY l.locations
ORDER BY `location_count` DESC
Try something like this:
SELECT l.location, l.address, l.telephone, COUNT(u.userID) AS [users favourable]
FROM location l
LEFT JOIN
users u
ON l.location = u.locationchosen
GROUP BY l.location, l.address, l.telephone
SELECT loc.*, countResult.usersFavourable
FROM location loc
LEFT JOIN
(
SELECT locationChoosen, COUNT(*) `usersFavourable`
FROM users
GROUP BY locationChoosen
) countResult ON loc.locations = countResult.locationChoosen
ORDER BY countResult.usersFavourable DESC, loc.locations
use this:
select count(userId) count,locations,address,telephone
from Table1 Left join Table2
on Table1.locations = Table2.location
group by locations order by count desc ;
see here.. link
SELECT LocationChosen, Count(*) FROM usersTable GROUP BY LocationChosen

mysql select a value within a select

I have an implementation messages system.
My problem is, I would like to know whether a user already has a thread with another user and if so what is the mid
I have a messages_recips table which look like this
---------------------------
| mid | seq | uid | status|
|--------------------------
| 4 | 1 | 1 | A |
| 4 | 1 | 2 | A |
---------------------------
if user id 1 having a thread with user id 2 I hold 2 rows with same mid.
I know I can create 2 sqls to achieve what I'm asking for, but I'm trying to do it in 1 sql.
As noted by Waqar Janjua, the key to this is a self-join query:
SELECT m1.mid
FROM messages_recips AS m1
JOIN messages_recips AS m2 ON m1.mid = m2.mid
WHERE m1.uid = 1
AND m2.uid = 2
I think you have to write a self-join query:
Select u.uid, u1.uid from tablename u
INNER JOIN tablename u1 on u.mid = u1.mid
You will get all the users who have the same mid.
In order to get only user1 and user2 records you have to place a where clause at the end of the query lik this.
Select u.uid, u1.uid from tablename u
INNER JOIN tablename u1 on u.mid = u1.mid
Where ( u.uid In ( 1,2 ) OR u1.uid In ( 1,2 ) ) ;

MySQL - Join 2 tables

I have 2 tables: users & balance.
I want to join the tables with all of the details from the user table (all fields of all tuples) with the most recent entry from the balance table (1 field linked by a user id).
Here is the structure of the tables:
balance:
+---------+
| Field |
+---------+
| dbid |
| userId |
| date |
| balance |
+---------+
users:
+-------------+
| Field |
+-------------+
| dbid |
| id |
| fName |
| sName |
| schedName |
| flexiLeave |
| clockStatus |
+-------------+
I have been trying for hours to do this and the closest I can get is to return a row for a single user:
SELECT u.*, b.balance, b.date
FROM users u, balance b
WHERE
u.id = b.userId AND
b.date = (SELECT MAX(date) FROM balance WHERE userId = 'A8126982');
Or I can select all users but not the most recent entry in the balance table:
SELECT u.*, b.balance, b.date
FROM users u, balance b
WHERE u.id = b.userId GROUP BY u.id;
I have tried many different queries and seem to be getting closer but I just can't get to where I want to be.
Any help would be appreciated.
You can use the first SQL you wrote but for all users:
SELECT u.*, b.balance, b.date
FROM users u JOIN balance b ON u.id = b.userId
WHERE b.date = (SELECT MAX(date) FROM balance WHERE userId = u.id);
This may not be the fastest way to get the result, but it'll give you what you need. I use similar queries in quite a few places in my app.