MySQL - select - max - group by - efficiency - mysql

I have a question regarding performance of the MySQL DBMS.
Perhaps a trivial matter.
There are two tables and I need to get result as below :
PLAYERS VISITS
ID | PLAYER_NAME ID | PLAYER_ID | SEEN
---------------- ---------------------------
1 | user 1 1 | 2 | 2012-12-12
2 | user 2 2 | 2 | 2012-12-13
3 | user 3 3 | 3 | 2012-12-13
4 | user 4 4 | 3 | 2012-12-14
5 | 3 | 2012-12-14
6 | 2 | 2012-12-15
RESULT:
ID | PLAYER_NAME | LAST_SEEN
----------------------------
1 | user 1 | NULL / 'NEVER'
2 | user 2 | 2012-12-15
3 | user 3 | 2012-12-14
4 | user 4 | NULL / 'NEVER'
My current query is :
SELECT
players.id,
players.player_name,
MAX(visits.seen) AS last_seen
FROM players
LEFT JOIN visits ON players.id = visits.player_id
GROUP BY players.id,players.player_name
Works for me but it seems to me that it should be a more efficient method.
It's just key part of a larger query.
Thomas

To make this join efficient there has to be an index on player_id in VISITS. Look into
`CREATE INDEX`
here.
To check the query efficiency you can always use:
EXPLAIN SELECT /* your select here */
Also if PLAYERS.ID is unique and primary key it's perfectly fine to group only by this id.
SELECT
players.id,
players.player_name,
MAX(visits.seen) AS last_seen
FROM players
LEFT JOIN visits ON players.id = visits.player_id
GROUP BY players.id
but your original query is perfectly fine. Make sure you fully understand GROUP BY and the consequences for columns not included in GROUP BY if you omit columns. This can have unintended consequences in other queries (where the same id doesn't mean the same name i.e.)

Related

How can I determine a score result based on the goal count in mysql?

Maybe this question is super specific but I can't really find a way to do it.
A bit of background. I'm studying, and In order to learn this I'm playing around counting stats from our Wednesday football matches with my friends.
Now in order to make data as normalized as possible and calculate everything programmatically, I have a table called matches and a table called goals.
Matches
+----+------------+
| id | date |
+----+------------+
| 1 | 05-01-2022 |
+----+------------+
| 2 | 12-01-2022 |
+----+------------+
Goals
+----+--------------+
| id | match | team |
+----+--------------+
| 1 | 1 | A |
+----+--------------+
| 2 | 1 | B |
+----+--------------+
| 3 | 1 | B |
+----+--------------+
| 4 | 2 | A |
+----+--------------+
| 5 | 2 | A |
+----+--------------+
So my objective here is to count the goals for each team in each match, compare both values to determine the winner and come up with a result like this:
Results
+-------+------------+
| match | result |
+-------+------------+
| 1 | B |
+-------+------------+
| 2 | A |
+-------+------------+
I'm currently doing this fairly straightforward using laravel on my app, but I'm trying to get it done through SQL queries only.
I also know that this would be much easier, if i simply added a goals_team_a, goals_team_b, result columns to my Matches table. If this is the actual correct solution is super easy to add, I just though at some point goals may differ from the score because of a typo or something and this is the way to be extra sure. Probably an overkill
The current structure of matches and goals is definitely flawed. If a given match id has no goals scored by either side there is no record of who played. By simply adding home and away teams to the match you have a more meaningful match entity and it becomes simple to query for the result.
Matches
id
kick_off
home_team_id
away_team_id
1
2022-01-05 10:00:00
1
2
2
2022-01-12 14:30:00
2
1
Teams
id
name
1
A
2
B
Additionally, if your goals table is as simple as suggested, it serves no purpose but I suspect this was simplification for the sake of providing an example. Assuming there is a player and time goal scored as part of the goal table, it has obvious value.
Goals
id
match_id
team_id
player_id
time_scored
1
1
1
1
10:01:23
2
1
2
2
10:17:38
3
1
2
2
11:02:44
4
2
1
1
15:42:53
5
2
1
1
15:47:18
With the teams identified in the match you can then use something like -
select m.id as match_id,
case
when count(distinct hg.id) > count(distinct ag.id) then ht.name
when count(distinct hg.id) < count(distinct ag.id) then at.name
else 'draw'
end as `result`
from matches m
join teams ht on m.home_team_id = ht.id
join teams at on m.away_team_id = at.id
left join goals hg on m.home_team_id = hg.team_id and m.id = hg.match_id
left join goals ag on m.away_team_id = ag.team_id and m.id = ag.match_id
group by m.id
to get -
match
result
1
B
2
A
Here's a db<>fiddle to play around with.

Different price for additional members of the same group

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".

MySQL Order by before group by in view (No Sub-Querys)

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
);

How to join multiple random values of different tables in a single query?

I have 3 tables (called results,users,games), the first one get data from the others, all with a primary auto-increment ID like this:
results
id | idusers | idgames
--------+---------+----------
| |
| |
| |
users
id | name |
--------+---------+
1 | todd |
2 | mario |
3 | luigi |
games
id | play |
--------+---------+
1 | game1 |
2 | game2 |
3 | game3 |
I wish to randomize users ID and games ID, and join together inside the results table like this:
results
id | idusers | idgames
--------+---------+----------
1 | 3 | 2
2 | 1 | 1
3 | 2 | 1 (also duplicates are ok)
I know for randomize the ID it's supposed to use SELECT * FROM users ORDER BY NEWID() and for join together different tables I need to use INNER JOIN,
but how to make it all working together in one single query?
One method in MySQL is to use rand() and a correlated subquery:
select idusers,
(select id from games order by rand() limit 1) as idgames
from users;
If you have a reasonable amount of data (more than a few thousand rows), then there are more efficient methods.

mysql Select question

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