This is a revised question of my previous question, because I dont think I asked it correctly.
I am creating a chat room in NodeJS, and in order to do that I have the following table that lists which users are in each room:
+----+--------+--------+--+
| id | roomID | userID | |
+----+--------+--------+--+
| 1 | 9 | 3 | |
| 2 | 9 | 4786 | |
| 3 | 9 | 7991 | |
| 4 | 7 | 3 | |
| 5 | 7 | 4786 | |
| 6 | 1 | 3 | |
+----+--------+--------+--+
I need to be able to select from the list the roomID from an array of users. So lets say, I want to get the roomID that users 4786 and 3 are using, It would return 7. If I wanted to get the roomID that users 4786,3 AND 7991 are using, it would return 9.
Okay so. In my previous question the answer I got was to use SELECT * FROM table WHERE userID IN (4786,3,7991). What I didnt realise (I dont use the IN statement that much) was that the IN statement was essentially a shortcut for the OR statement.
Which leads me to the problem, The first thing I tried was OR and because it doesnt explicitly match each userID in the table, the results are unreliable.
I've created an SQLFiddle here.
I want to be able to say: Give me the unique roomID for these users and and these users only. It doesnt matter if they are involved in other rooms.
I hope i've made myself a bit more understandable.
SQL DEMO
SELECT roomId, COUNT(CASE WHEN `userID` IN (4786,3)
THEN 1
END) total_match
FROM `chat-room-users`
GROUP BY roomId
HAVING total_match = 2 -- because are 2 users in (4786,3)
AND total_match = COUNT(`userID`)
Lets say you put your users in a table search_user to be more generic your query become:
SELECT roomId, COUNT(CASE WHEN `userID` IN (SELECT userID FROM search_users)
THEN 1
END) total_match
FROM `chat-room-users`
GROUP BY roomId
HAVING total_match = (SELECT COUNT(*) FROM search_users)
AND total_match = COUNT(`userID`)
Related
I have a simple table like this:
group | name | price
1 | john |
2 | mike |
3 | paul |
1 | sean |
4 | jack |
2 | brad |
5 | mick |
1 | bill |
4 | chad |
I have two different price values where 100EUR is for a first member of a group and 50EUR is for all additional members of that same group.
Detailed explanation. If a group has only one member, that member gets a price of 100EUR. If a group has multiple members, the first member gets a price of 100EUR, and all additional members of that same group get a price of 50EUR. There can be unlimited number of groups that will be added additionally.
The result should be like this:
group | name | price
1 | john | 100
2 | mike | 100
3 | paul | 100
1 | sean | 50
4 | jack | 100
2 | brad | 50
5 | mick | 100
1 | bill | 50
4 | chad | 50
I'd need a query which would be able to INSERT/UPDATE all missing price fields whenever I manually run it.
Thank you in advance for looking into that matter.
After a lot of trial and error I found a perfect fully functional solution, based on daviid's clever method. The issue with mysql is that by it's structure won't update tables with select methods as subquery. However, self-join (join or inner join) methods can be used instead in this case. I also had to add auto-incremental id to that table, so the final table structure is:
id | group_id | name | price
1 | 1 | john |
2 | 2 | mike |
3 | 3 | paul |
4 | 1 | sean |
5 | 4 | jack |
6 | 2 | brad |
7 | 5 | mick |
8 | 1 | bill |
9 | 4 | chad |
---
SET SQL_SAFE_UPDATES=0;
UPDATE table_name
SET price = 50;
UPDATE table_name AS a
JOIN
( SELECT id
FROM table_name
GROUP BY group_id
HAVING COUNT(*) >= 1
) AS b
ON a.id = b.id
SET a.price = 100;
Thanks also to Cody and Barmar for usable hints...
A partial answer: you can GROUP BY your "group" field and tack on a HAVING COUNT(group) > 1 to determine if that group has more than 1 member.
That is, to see all groups with more than one member it would look like:
SELECT
group
FROM table
GROUP BY group
HAVING COUNT(group) > 1
That will just tell you which groups have multiple members. Without another way to ensure ordering you cannot tell which member is "first" in their group and thus should be priced at 100 and all others priced at 50.
The following queries are not tested and might contain syntax errors. But they are good enough to understand the principle. There are many possible ways to achieve your result.
Here is my take: I would make use of one query to UPDATE the price on every row and set it to 50 whether it is the first group member or not. >table_name<, of course, needs to be changed to the name of your mentioned table.
UPDATE >table_name<
SET price = 50;
Then I would take care of each individual group and the respective first member by running the following query. Adapt the query to each group by changing the >groupId<.
UPDATE >table_name<
SET price = 100
WHERE id = (
SELECT id
FROM >table_name<
WHERE group = >groupId<
ORDER BY id
LIMIT 1
);
Take a look a the nested query: It queries the table for all members of only one group, orders them in ascending order and only returns an id per member. By applying LIMIT to the query, the result will just be the first group member's id. The resulting id can then be used in the other query to update the price and set it to 100.
But be careful: If you insert/delete (new) members with an id that is not just counting up, this query might select a "new first member".
I realize this question has been asked quite a few times, however i haven't managed to find a working solution for my case.
Essentially my problem arises because MySQL Doesn't allow sub-querys in views.
I found a few workarounds but they don't seem to work.
In more detail...
My first table (competitions) stores a users competitions:
id_tournament | id_competition | id_user | result
-------------------------------------------------
1 | 1 | 1 | 10
1 | 1 | 2 | 30
1 | 2 | 1 | 20
1 | 2 | 3 | 50
1 | 3 | 2 | 90
1 | 3 | 3 | 100
1 | 3 | 4 | 85
In this example there are three competitions:
(
user1 vs. user2,
user1 vs. user3,
user2 vs. user3 vs. user4
)
My problem is that i need to define a view that gives me the winners in each competition.
Expected Result:
id_tournament | id_competition | id_winner
------------------------------------------
1 | 1 | 2
1 | 2 | 3
1 | 3 | 3
This can be solved with the query:
SELECT
id_tournament,
id_competition,
id_user as id_winner
FROM (
SELECT * FROM competitions ORDER BY result DESC
) x GROUP BY id_tournament, id_competition
This query however uses a subquery (not allowed in views), so my first solution was to define a 'helper view'as :
CREATE VIEW competitions_helper AS (
SELECT * FROM competitions ORDER BY result DESC
);
CREATE VIEW competition_winners AS (
SELECT
id_tournament,
id as id_competition,
id_user as winner
FROM competitions_helper GROUP BY id_tournament, id_competition
);
However this does not seem to give the correct result.
It's result will then be:
id_tournament | id_competition | id_winner
------------------------------------------
1 | 1 | 1
1 | 2 | 1
1 | 3 | 1
What i don't understand is why it works when i use Sub-querys and why it gives a different result with the exact same statement in a view.
Any help is appreciated, thanks alot.
This is due to the GROUP BY behaviour.
In this case, the server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate, which is probably not what you want.
I would solve the problem in this way:
CREATE VIEW competitions_helper AS (
SELECT id_tournament,
id_competition,
MAX(result) as winning_result
FROM competitions
GROUP BY id_tournament,
id_competition
);
CREATE VIEW competition_winners AS (
SELECT c.id_tournament,
c.id_competition,
c.id_user
FROM competitions c
INNER JOIN competitions_helper ch
ON ch.id_tournament = c.id_tournament
AND ch.id_competition = c.id_competition
AND ch.winning_result = c.result
);
I would like to know how to exclude a mysql group, i did research on here for some time but just dont make it right.
I tried
SELECT * FROM booking GROUP BY BookingId HAVING Status!="Cancellation"
which obviously doesn't work.
Example Database looks like this:
+----+-----------+--------------+
| id | BookingId | Status |
+----+-----------+--------------+
| 1 | 1 | Booked |
| 2 | 1 | Cancellation |
| 3 | 2 | Booked |
+----+-----------+--------------+
I would like to group them by BookingId and if one of the found entries got the Status Cancellation this group shouldnt show up, so from above just the id 3 would be seen.
You need to use aggregate functions like sum in a having clause
SELECT BookingId
FROM booking
GROUP BY BookingId
HAVING sum(Status = 'Cancellation') = 0
I am coding a social networking type website, users have friends and I am trying to create a news feed with actions that their friends have done
I have three tables
Users
id | username
---------------------
1 | john
2 | nicole
3 | bob
Friends
id | uid | who
----------------------------
1 | 1 | 2
2 | 2 | 1
3 | 2 | 3
4 | 3 | 2
5 | 3 | 1
6 | 1 | 3
Actions
id | owner | to_id | message
-------------------------------------------
1 | 3 | 2 | 'hello'
2 | 3 | 1 | 'yoooo'
Since 'john' is friends with 'bob, 'john' should be able to see 'bob' action to 'nicole'
EDIT:: I also want to get the actions from the other direction, if 'nicole' sends an action to 'bob'
My current solution is:
have a string that contains all the friends of a user: $friends = '1,2,3,4....etc'
query:
SELECT
`Actions`.*
FROM
`Actions`
WHERE
(`Actions`.`to_id` IN (${friends}) OR `Actions`.`from_id` IN (${friends}))
AND
( `Actions`.`to_id` != (${logged_id} AND `Actions`.`from_id` != (${logged_id})
ORDER BY
`Actions`.`time` ASC
LIMIT
15
The above query works, but my problem is when users start have hundreds of friends this query will be horrendously slow, what can I do as an alternative to prevent this?
If you want to try a different and better solution, please try using any popular graph database. It is well suited for your current requirement.
What about using a JOIN to achieve this?
SELECT actions.*
FROM actions a
JOIN friends f ON f.who = a.owner
WHERE f.uid = (current user's ID)
LIMIT 15
(Returns 15 actions owned by friends of the current user.)
I can't get on the right track with this, any help would be appreciated
I have one table
+---+----------+---------+-----------+
|id | match_id | team_id | player_id |
+---+----------+---------+-----------+
| 1 | 9 | 10 | 5 |
| 2 | 9 | 10 | 7 |
| 3 | 9 | 10 | 9 |
| 4 | 9 | 11 | 12 |
| 5 | 9 | 11 | 15 |
| 6 | 9 | 11 | 18 |
+---+----------+---------+-----------+
I want to select these with a where on the match_id and both team id's so the output will be
+---------+-------+------+---------+---------+
| MATCHID | TEAMA | TEAMB| PLAYERA | PLAYERB |
+---------+-------+------+---------+---------+
| 9 | 10 | 11 | 5 | 12 |
| 9 | 10 | 11 | 7 | 15 |
| 9 | 10 | 11 | 9 | 18 |
+---------+-------+------+---------+---------+
It's probably very simple, but i'm stuck..
thanks in advance
p.s. seemed to forgot a column on my first post, sorry
I think you should redesign your table though, maybe the format that you want as output should be your table design.
With your design, it's possible to have three or more teams playing against each other...
So. I gave this another try (coming from Oracle myself, I really miss ROWNUM here).
The following query should give you the result you want to have, but I'm not sure if you should really do that in pure SQL. Maybe you could just combine the teams in your client?
SELECT m1.match_id, m1.team_id, m2.team_id, m1.player_id, m2.player_id
FROM (
SELECT match_id, team_id, player_id,
-- get ranking
( SELECT 1 + count(*)
FROM matches m1b
WHERE m1b.match_id = m1a.match_id
AND m1b.team_id = m1a.team_id
AND m1b.player_id < m1a.player_id) rank
FROM matches m1a
WHERE m1a.team_id = (SELECT MIN(team_id) -- first team
FROM matches
WHERE match_id = m1a.match_id)
) m1,
(
SELECT match_id, team_id, player_id,
-- get ranking
( SELECT 1 + count(*)
FROM matches m2b
WHERE m2b.match_id = m2a.match_id
AND m2b.team_id = m2a.team_id
AND m2b.player_id < m2a.player_id) rank
FROM matches m2a
WHERE m2a.team_id = (SELECT MAX(team_id) -- second team
FROM matches
WHERE match_id = m2a.match_id)
) m2
WHERE m1.match_id = m2.match_id
AND m1.rank = m2.rank
What I do here is:
Select all ROWs from the teams with lower team_id per match and give them a ranking (1 to 3 per match)
Select all ROWs from the teams with higher team_id per match and give them a ranking (1 to 3 per match)
Combine those two queries in one result, where the match_id and the ranking match
match is a reserve word in mysql. table name used here is matchs
select match_id, sum(if(id=1, team_id,0))team_A, sum(if(id=2,team_id,0)) team_b
from matchs
group by match_id;
+----------+--------+--------+
| match_id | team_A | team_b |
+----------+--------+--------+
| 5 | 9 | 10 |
+----------+--------+--------+
1 row in set (0.00 sec)
I'm not sure if the previous answers will give you what you're looking for, at least I took your question to mean something else - perhaps you could clarify the purpose of the table and the query. If the table associates teams with matches and you want a query to show you all the teams associated with one match, then your query should be
select team_id as teams from table where match_id = id_here
which would give you back (for id_here being 5)
teams
-----
9
10
Take a look at the url below, It is exactly what you want but is in t-sql. It can merge any number of rows.
Converting fields into columns