Selecting from four tables and grouping some results into one column - mysql

sports_games Table
==========================================================================
| game_id | team_id | game_type | time_started | time_ended | planned_by |
|========================================================================|
| 1 | 1 | 1 | 1640799417 | 1641146196 | 1 |
| 2 | 2 | 1 | 1640971535 | 1641579516 | 1 |
| 3 | 1 | 2 | 1640971553 | 1641582723 | 8 |
| 4 | 2 | 2 | 1640971585 | 1641404242 | 9 |
| 5 | 3 | 4 | 1641061431 | 1641754479 | 12 |
==========================================================================
I have been able to combine the above table, with the below table;
game_types Table
==============================
| game_type | game_type_name |
|============================|
| 1 | football |
| 2 | baseball |
| 3 | golf |
| 4 | tennis |
==============================
With the following SQL
SELECT *
FROM sport_games
INNER JOIN game_types
ON sport_games.game_type = game_types.game_type
However, I am now finding myself going round and around, attempting potential solution after the other. I need to include data from two more tables, participents and user_basic.
The participents table simply has the game_id and user_id columns, one row per participant, both combined to be the primary key, and within user_basic table, you can find user_id along with their user_name.
For the planned_by column in the firt table, this is also a user_id which I want to obtain their username too.
With all the participants, I would like them to be within one column grouped by their game.
Things That I've Tried
group_concat( .. ), perhaps I was using this completely wrong, I could only achieve this grouping the entire results set from the other table and could not seem to figure out how to use this in a join correctly.
STRING_AGG( ... ) Initially this lead down a path of turning out that I could not use this as I was running on 5.5 MariaDB, which lead down struggling path for me to update. Now I'm using MariaDB 10.3, however I still cannot seem to get the simplest function to work for this, despite going through several Stack Overflows and whatnot.
I feel like I'm at a bit of a loss, any progress I seem to make forward, I am taking two steps back! And then there is using the JOIN amongst these, I'm not sure if I am wording my searches right to place me on the right path, so my question title might be a bit ambiguous right now, sorry if this is the case! - Please suggest otherwise, thank you.
Example Desired Outcome
"2": { // <- game_id
"game_type": 2,
"game_type_name": "baseball",
"participants": {
"1": "username_1",
"2": "username_21",
"3": "username_3",
"4": "username_4",
},
"time_started": 1641061431,
"time_completed": 1641754479,
"planned_by": {
"851730": "username_1",
}
}

You must join properly the tables and the table user_basic twice: to get the participants and the user who planned the game.
For MariaDB 10.5+ you can use JSON_ARRAYAGG():
SELECT sg.game_id,
sg.game_type,
gt.game_type_name,
JSON_ARRAYAGG(JSON_OBJECT(ub.user_id, ub.user_name)) participants,
sg.time_started,
sg.time_ended,
JSON_OBJECT(sg.planned_by, pb.user_name) planned_by
FROM sports_games sg
INNER JOIN user_basic pb ON pb.user_id = sg.planned_by
INNER JOIN game_types gt ON gt.game_type = sg.game_type
INNER JOIN participents p ON p.game_id = sg.game_id
INNER JOIN user_basic ub ON ub.user_id = p.user_id
GROUP BY sg.game_id;
For previous versions use GROUP_CONCAT():
SELECT sg.game_id,
sg.game_type,
gt.game_type_name,
CONCAT('{', GROUP_CONCAT(ub.user_id, ' : ', ub.user_name SEPARATOR ', '), '}') participants,
sg.time_started,
sg.time_ended,
CONCAT('{', GROUP_CONCAT(DISTINCT sg.planned_by, ' : ', pb.user_name), '}') planned_by
FROM sports_games sg
INNER JOIN user_basic pb ON pb.user_id = sg.planned_by
INNER JOIN game_types gt ON gt.game_type = sg.game_type
INNER JOIN participents p ON p.game_id = sg.game_id
INNER JOIN user_basic ub ON ub.user_id = p.user_id
GROUP BY sg.game_id;
See the demo.

Is time_ready an existing column or calculated from other columns? I don't see time_ready in any of the provided tables.
SELECT
game_types.game_type
, game_types.game_type_name
, participents.user_id
, user_basic.user_name
, sports_games.time_started
, sports_games.time_ended AS time_completed
, sports_games.planned_by
FROM sports_games
INNER JOIN game_types
ON sports_games.game_type = game_types.game_type
INNER JOIN participents
ON sports_games.game_id = participents.game_id
INNER JOIN user_basic
ON participants.user_id = user_basic.user_id
ORDER BY
game_types.game_type

Related

MySQL - Retrieve results even if join is null

I have the following sql working with 1 minor issue that I need help with. I am needing the results to show even if one of the joined tables is null. I have tried using a left/right join, but it didn't seem to make a difference, what do I need to use to make this work?
As it is now, the query will work unless the record in the table dx_code_patient has no match, I need it show the results even if there are no matching records in that table.
SELECT
group_concat(distinct current_dx.dx_code_with, ': ', current_dx.description SEPARATOR ' - ') AS current_dxc,
group_concat(distinct pending_dx.dx_code_with, ': ', pending_dx.description SEPARATOR ' - ') AS pending_dxc,
p.id, p.first_name, p.last_name
FROM patients AS p
INNER JOIN tmp_dx_code_patient AS tmp_dx
ON tmp_dx.patient_id = p.id
INNER JOIN dx_code_patient AS cdx
ON cdx.patient_id = p.id
INNER JOIN dx_codes AS current_dx
ON current_dx.id = cdx.dx_code_id
INNER JOIN dx_codes AS pending_dx
ON pending_dx.id = tmp_dx.dx_code_id
GROUP BY p.id
ORDER BY tmp_dx.created_at asc
current results:
+----------------+--------------+----+------------+-----------+
| current_dxc | pending_dxc | id | first_name | last_name |
+----------------+--------------+----+------------+-----------+
| def: something | 123: message | 2 | Bob | Smith |
+----------------+--------------+----+------------+-----------+
Expected
+----------------+---------------+----+------------+------------+
| current_dxc | pending_dxc | id | first_name | last_name |
+----------------+---------------+----+------------+------------+
| null | ghy: hi | 1 | Mike | Jones |
+----------------+---------------+----+------------+------------+
| def: something | 123: message | 2 | Bob | Smith |
+----------------+---------------+----+------------+------------+
| null | 432: question | 3 | John | Doe |
+----------------+---------------+----+------------+------------+
JOIN does not return NULL results. My guess is that you mean no rows match. If so, a LEFT JOIN should solve your problem. Use it throughout the FROM clause:
FROM patients p LEFT JOIN
tmp_dx_code_patient tmp_dx
ON tmp_dx.patient_id = p.id LEFT JOIN
dx_code_patient cdx
ON cdx.patient_id = p.id LEFT JOIN
dx_codes current_dx
ON current_dx.id = cdx.dx_code_id LEFT JOIN
dx_codes pending_dx
ON pending_dx.id = tmp_dx.dx_code_id

MySQL - Working Back Through Multiple Tables by FK

I have a database which isn't of my own creation. I need to extract some specific data from it but I'm struggling to get my head around how to get the data back without doing multiple queries and looping over the result set in my code. I've looked around at other questions but haven't been able to get very far.
My data structure is (very condensed with non-relevant rows and columns omitted):
MyDb.Source
+--------+-------------+--------------------------------------+
| ID | SOURCE_TYPE | URL |
+--------+-------------+--------------------------------------+
| 10 | 3 | https://en.wikipedia.org |
+--------+-------------+--------------------------------------+
MyDb.Resource
+--------+------------+--------------------------------------+
| ID | SOURCE_FK | IDENTIFIER |
+--------+------------+--------------------------------------+
| 1 | 10 | All_Saints_Church,_Marple |
+--------+------------+--------------------------------------+
MyDb.Item_Base
+--------+-------------+--------------------------------------+
| ID | RESOURCE_FK | ITEM_TITLE |
+--------+-------------+--------------------------------------+
| 55 | 1 | All Saints Church, Marple |
+--------+-------------+--------------------------------------+
MyDb.Item
+--------+-------------+--------------------------------------+
| ID | BASE_FK | ITEM_DESCRIPTION |
+--------+-------------+--------------------------------------+
| 120 | 55 | Foo bar |
+--------+-------------+--------------------------------------+
Source - Resource is 1 to many.
Resource - Base is 1 to 1.
Item_Base - Item is 1 to 1.
What am I trying to do?
I want as few queries as possible to work back from MyDb.Source to find all items related to it. The only information I have in my hand is the ID for the source, which is 10. I want to end up with a result set of Item.ID which contains only those where Source.ID is 10.
I think you can just inner join the four tables together in a single query. This should be safe, because in order for a relationship to exist between a source and an item, the latter must be reachable via a key relationship.
SELECT
t1.ID AS source_id,
t4.*
FROM Source t1
INNER JOIN Resource t2
ON t1.ID = t2.SOURCE_FK
INNER JOIN Item_Base t3
ON t2.ID = t3.RESOURCE_FK
INNER JOIN Item t4
ON t3.ID = t4.BASE_FK
WHERE
t1.ID = 10
You need INNER JOINs in your query. Then it's possible in one simple query:
SELECT
i.ID
FROM
Source s
INNER JOIN Resource r
ON s.ID = r.SOURCE_FK
INNER JOIN Item_Base b
ON r.ID = b.RESOURCE_FK
INNER JOIN Item i
ON b.ID = i.BASE_FK
WHERE
s.ID = 10
See https://dev.mysql.com/doc/refman/5.7/en/join.html and http://www.mysqltutorial.org/mysql-inner-join.aspx for more info and examples relating to joins and how to use them.

Mysql select rows with same id's (3 tables)

I have the following tables:
'blog_content'
'blog_media'
'blog_media_content'
| blog_id | media_id |
========================
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 3 | 4 |
I want to select all blog_media.uri's where blog_media.media_id equals blog_media_content.blog_id.
Please help me to achieve my aim.
An inner join between blog_media and blog_media_content tables would suffice.
SELECT
bm.uri
FROM blog_media bm
INNER JOIN blog_media_content bmc ON bm.media_id = bmc.media_id
WHERE bmc.blog_id =3;
Note:
If you need any additional information from blog table then you need an additional inner join like below:
...INNER JOIN blog_table b ON bmc.blog_id = b.blog_id...
EDIT:
In order to get records for all blog_ids :
SELECT
bm.uri
FROM blog_media bm
INNER JOIN blog_media_content bmc ON bm.media_id = bmc.media_id
ORDER BY bmc.blog_id;

Can I be selective on what rows I join on in MySQL

Suppose I have two tables, people and emails. emails has a person_id, an address, and an is_primary:
people:
id
emails:
person_id
address
is_primary
To get all email addresses per person, I can do a simple join:
select * from people join emails on people.id = emails.person_id
What if I only want (at most) one row from the right table for each row in the left table? And, if a particular person has multiple emails and one is marked as is_primary, is there a way to prefer which row to use when joining?
So, if I have
people: emails:
------ -----------------------------------------
| id | | id | person_id | address | is_primary |
------ -----------------------------------------
| 1 | | 1 | 1 | a#b.c | true |
| 2 | | 2 | 1 | b#b.c | false |
| 3 | | 3 | 2 | c#b.c | true |
| 4 | | 4 | 4 | d#b.c | false |
------ -----------------------------------------
is there a way to get this result:
------------------------------------------------
| people.id | emails.id | address | is_primary |
------------------------------------------------
| 1 | 1 | a#b.c | true |
| 2 | 3 | c#b.c | true | // chosen over b#b.c because it's primary
| 3 | null | null | null | // no email for person 3
| 4 | 4 | d#b.c | false | // no primary email for person 4
------------------------------------------------
You got it a bit wrong, how left/right joins work.
This join
select * from people join emails on people.id = emails.person_id
will get you every column from both tables for all records that match your ON condition.
The left join
select * from people left join emails on people.id = emails.person_id
will give you every record from people, regardless if there's a corresponding record in emails or not. When there's not, the columns from the emails table will just be NULL.
If a person has multiple emails, multiple records will be in the result for this person. Beginners often wonder then, why the data has duplicated.
If you want to restrict the data to the rows where is_primary has the value 1, you can do so in the WHERE clause when you're doing an inner join (your first query, although you ommitted the inner keyword).
When you have a left/right join query, you have to put this filter in the ON clause. If you would put it in the WHERE clause, you would turn the left/right join into an inner join implicitly, because the WHERE clause would filter the NULL rows that I mentioned above. Or you could write the query like this:
select * from people left join emails on people.id = emails.person_id
where (emails.is_primary = 1 or emails.is_primary is null)
EDIT after clarification:
Paul Spiegel's answer is good, therefore my upvote, but I'm not sure if it performs well, since it has a dependent subquery. So I created this query. It may depend on your data though. Try both answers.
select
p.*,
coalesce(e1.address, e2.address) AS address
from people p
left join emails e1 on p.id = e1.person_id and e1.is_primary = 1
left join (
select person_id, address
from emails e
where id = (select min(id) from emails where emails.is_primary = 0 and emails.person_id = e.person_id)
) e2 on p.id = e2.person_id
Use a correlated subquery with LIMIT 1 in the ON clause of the LEFT JOIN:
select *
from people p
left join emails e
on e.person_id = p.id
and e.id = (
select e1.id
from emails e1
where e1.person_id = e.person_id
order by e1.is_primary desc, -- true first
e1.id -- If e1.is_primary is ambiguous
limit 1
)
order by p.id
sqlfiddle

MySQL selective GROUP BY, using the maximal value

I have the following (simplified) three tables:
user_reservations:
id | user_id |
1 | 3 |
1 | 3 |
user_kar:
id | user_id | szak_id |
1 | 3 | 1 |
2 | 3 | 2 |
szak:
id | name |
1 | A |
2 | B |
Now I would like to count the reservations of the user by the 'szak' name, but I want to have every user counted only for one szak. In this case, user_id has 2 'szak', and if I write a query something like:
SELECT sz.name, COUNT(*) FROM user_reservations r
LEFT JOIN user_kar k ON k.user_id = r.user_id
LEFT JOIN szak s ON r.szak_id = r.id
It will return two rows:
A | 2 |
B | 2 |
However I want to every reservation counted to only one szak (lets say the highest id only). I tried MAX(k.id) with HAVING, but seems uneffective.
I would like to know if there is a supported method for that in MySQL, or should I first pick all the user ID-s on the backend site first, check their maximum kar.user_id, and then count only with those, removing them from the id list, when the given szak is counted, and then build the data back together on the backend side?
Thanks for the help - I was googling around for like 2 hours, but so far, I found no solution, so maybe you could help me.
Something like this?
SELECT sz.name,
Count(*)
FROM (SELECT r.user_id,
Ifnull(Max(k.szak_id), -1) AS max_szak_id
FROM user_reservations r
LEFT OUTER JOIN user_kar k
ON k.user_id = r.user_id
GROUP BY r.user_id) t
LEFT OUTER JOIN szak sz
ON sz.id = t.max_szak_id
GROUP BY sz.name;