MySQL - Retrieve results even if join is null - mysql

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

Related

Selecting from four tables and grouping some results into one column

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

How to properly add an additional column in a SELECT statement in MySQL?

I would like to extract the number of attendances (i.e., COUNT()) of "Coaches" at "Shows" happening during two separate months: March and April. I managed to create a query that collects that number over only one of the months. In addition, via slightly modifying the query, the numbers over the second month can be found easily. But how do I merge them into one table containing both columns?
So, given the two queries and resulting tables below, how would one "append" the result of Query 2 to the result of Query 1? In other words, how would one combine their respective SELECT statements?
I included links to the SQL fiddle in case you need them.
Thank you in advance.
SQL Fiddle
Query 1:
SELECT C.*, COUNT(CIS.idCoach) AS MarchNumOfShows
FROM Coach AS C
LEFT JOIN
(
CoachInShow AS CIS
LEFT JOIN
TVShow AS S
ON S.idShow = CIS.idShow
)
ON C.idCoach = CIS.idCoach AND S.airDate LIKE '_____04___'
GROUP BY C.idCoach
Results:
| idCoach | name | surname | MarchNumOfShows |
|---------|-----------|---------|-----------------|
| 1 | Stephen | Hawking | 5 |
| 2 | Nicholas | Cage | 7 |
| 3 | Sigourney | Weaver | 6 |
Query 2 (Minimal difference, querying for April instead of March):
SELECT COUNT(CIS.idCoach) AS AprilNumOfShows
FROM Coach AS C
LEFT JOIN
(
CoachInShow AS CIS
LEFT JOIN
TVShow AS S
ON S.idShow = CIS.idShow
)
ON C.idCoach = CIS.idCoach AND S.airDate LIKE '_____05___'
GROUP BY C.idCoach
Results:
| AprilNumOfShows |
|-----------------|
| 8 |
| 7 |
| 10 |
Wanted:
| idCoach | name | surname | MarchNumOfShows | AprilNumOfShows |
|---------|-----------|---------|-----------------|-----------------|
| 1 | Stephen | Hawking | 5 | 8 |
| 2 | Nicholas | Cage | 7 | 7 |
| 3 | Sigourney | Weaver | 6 | 10 |
You are very close, the last step you missed is simply combine MarchNumOfShows and AprilNumOfShows with left join.
like below codes (or look into the Sql Fiddle ):
SELECT C.idCoach, C.name, C.surname, COUNT(distinct CIS4.idShow) AS MarchNumOfShows
, COUNT(distinct CIS5.idShow) AS AprilNumOfShows
FROM Coach AS C
LEFT JOIN
(
CoachInShow AS CIS4
LEFT JOIN
TVShow AS S4
ON S4.idShow = CIS4.idShow
)
ON C.idCoach = CIS4.idCoach AND S4.airDate LIKE '_____04___'
LEFT JOIN
(
CoachInShow AS CIS5
LEFT JOIN
TVShow AS S5
ON S5.idShow = CIS5.idShow
)
ON C.idCoach = CIS5.idCoach AND S5.airDate LIKE '_____05___'
GROUP BY C.idCoach;
And below is another way to get the same output (or look into SQL Fiddle):
SELECT C.idCoach, C.name, C.surname,
sum(case when DATE_FORMAT(airDate,'%M')='April' then 1 else null end ) AS AprilNumOfShows,
sum(case when DATE_FORMAT(airDate,'%M')='May' then 1 else null end ) AS MayNumOfShows
FROM Coach AS C
LEFT JOIN
(
CoachInShow AS CIS
LEFT JOIN
TVShow AS S
ON S.idShow = CIS.idShow
)
ON C.idCoach = CIS.idCoach
GROUP BY C.idCoach;
one way to do it is with a case:
select *,
sum(case when airdate like "%03%" then 1 else 0 end) as March,
sum(case when airdate like "%04%" then 1 else 0 end) as April
...

Selecting Categories from a joined table and concatenating to form listing results

I am a bit confused about using GROUP_CONCAT in MySQL.
To explain, I am working on a listings directory, sort of like Yelp for example. A user filters different criteria and is returned with different places.
For example, if a user wants to see all places that have been categorized (or tagged) as "Restaurant", or maybe "Lounge" then they will see only those places, but also they should be presented with any other tagged categories.
To do this, I have built some tables in my database. The main tables that relate to this question are the tables places and categories and place_category_rel as such:
places table:
+----+------------------+
| id | name |
+----+------------------+
| 1 | Johns Restaurant |
+----+------------------+
| 2 | Jims Place |
+----+------------------+
| 3 | Cafe Luna |
+----+------------------+
place_category_rel table
+----------+-------------+
| place_id | category_id |
+----------+-------------+
| 1 | 1 |
+----------+-------------+
| 1 | 2 |
+----------+-------------+
categories table
+----+------------+
| id | name |
+----+------------+
| 1 | Lounge |
+----+------------+
| 2 | Restaurant |
+----+------------+
| 3 | Night Club |
+----+------------+
To obtain all places I use the following query:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p
LEFT JOIN places_categories_rel rel ON p.id=rel.place_id
LEFT JOIN categories cat ON cat.id=rel.category_id
GROUP BY p.id
Which gets me this result set:
+----+------------------+--------------------+
| id | name | categories |
+----+------------------+--------------------+
| 1 | Johns Restaurant | Lounge,Restaurant |
+----+------------------+--------------------+
| 2 | Jims Place | Null |
+----+------------------+--------------------+
| 3 | Cafe Luna | Null |
+----+------------------+--------------------+
The above works great. However, when I add a WHERE clause to filter down on places that are categorized as "Lounge" , I lose the other category:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p
LEFT JOIN places_categories_rel rel ON p.id=rel.place_id
LEFT JOIN categories cat ON cat.id=rel.category_id
WHERE cat.name = 'Lounge'
GROUP BY p.id
Result:
+----+------------------+------------+
| id | name | categories |
+----+------------------+------------+
| 1 | Johns Restaurant | Lounge |
+----+------------------+------------+
what I would like is this result set instead:
+----+------------------+--------------------+
| id | name | categories |
+----+------------------+--------------------+
| 1 | Johns Restaurant | Lounge, Restaurant |
+----+------------------+--------------------+
Can someone point me in the right direction? Thanks.
If I'm understanding correctly, you are trying to get all categories for a given place if they have a specific category. Assuming so, one option is to use exists:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p
LEFT JOIN places_categories_rel rel ON p.id=rel.place_id
LEFT JOIN categories cat ON cat.id=rel.category_id
WHERE EXISTS (
SELECT 1
FROM places_categories_rel pcr
JOIN categories c ON c.id=pcr.category_id
WHERE name = 'Lounge' AND p.id=pcr.place_id
)
GROUP BY p.id
Not sure you need outer joins in this case either -- probably would work the same with inner joins.
When using a LEFT JOIN, conditions on all but the first table should be in the ON clause:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p LEFT JOIN
places_categories_rel rel
ON p.id = rel.place_id LEFT JOIN
categories cat
ON cat.id = rel.category_id AND cat.name = 'Lounge'
GROUP BY p.id;
Remember: A LEFT JOIN returns all rows in the first table, regardless of whether the ON clause returns true, false, or NULL. If the ON condition is not true, then all columns in the second (and subsequent) tables are NULL.
When you test for cat.name = 'Lounge' in the WHERE clause, cat.name is NULL for the rows that do not match -- hence they are filtered out.
EDIT:
If you only want the category of lounge in the results (with others), then just add a having clause:
SELECT p.id,
p.name,
group_concat(DISTINCT cat.name separator ',') as categories
FROM places p LEFT JOIN
places_categories_rel rel
ON p.id = rel.place_id LEFT JOIN
categories cat
ON cat.id = rel.category_id
GROUP BY p.id
HAVING SUM(cat.name = 'Lounge') > 0;
I think this is the simplest approach.

Mysql SELECT display only 1 result instead of multiple from 3 tables

I try to make one SQL request where I get all data from multiple tables. Problem start when one user have for example more "enforcement". But first lets look on code:
SELECT c.id, c.firstname, c.surname, c.email, c.process, c.search_work, c.note,
MAX(CASE WHEN cl.languageID = 1 THEN cl.skill ELSE '-' END)AS 'en',
MAX(CASE WHEN cl.languageID = 2 THEN cl.skill ELSE '-' END)AS 'ge',
ce.enforcement
FROM candidates AS c
LEFT JOIN candidates_language AS cl ON c.id = cl.candidates_id
LEFT JOIN candidates_enforcement as ce on c.id = ce.candidates_id
GROUP BY c.id, c.firstname, c.surname, c.email
As you can see from here I search above multiple tables with using foreign key on candidates ID.
For this purpouse here is how 2 tables looks like:
candidates
------------------------------------------------------------------------
| id | firstname | surname | email |
------------------------------------------------------------------------
| 22 | John | Doe | john#doe.com |
------------------------------------------------------------------------
| 23 | Peter | Miller | doe#john.com |
------------------------------------------------------------------------
candidates_enforcement
--------------------------------------------------
| id | candidates_id | enforcement |
--------------------------------------------------
| 1 | 22 | Advocate |
--------------------------------------------------
| 2 | 22 | Programmer |
--------------------------------------------------
| 3 | 23 | IT Admin |
--------------------------------------------------
candidates_id = foreign key from candidates. With my SQL request above result should looks like:
---------------------------------------------------------------------------------
| id | firstname | surname | email | enforcement
---------------------------------------------------------------------------------
| 22 | John | Doe | john#doe.com | Advocate, Programmer |
---------------------------------------------------------------------------------
| 23 | Peter | Miller | doe#john.com | IT Admin
---------------------------------------------------------------------------------
Unfortunately it display me ALWAYS only 1 result from "enforcement". So for cancidate with id 22 it is Advocate not Advocate, Programmer
Is there a chance someone can help me to find a way how to fix this?
Thanks
p.s. Working demo on FIDDLE
http://sqlfiddle.com/#!9/25b1b/1
You could use GROUP_CONCAT like this:
SELECT
candidates.id,
candidates.firstname,
candidates.surname,
candidates.email,
group_concat(DISTINCT candidates_enforcement.enforcement)
FROM
candidates
LEFT JOIN candidates_enforcement
ON candidates.id = candidates_enforcement.candidates_id
GROUP BY
candidates.id,
candidates.firstname,
candidates.surname,
candidates.email
Reference:
GROUP_CONCAT(expr)
You can use Group_Concat with the Distinct option
SELECT c.id,c.firstname,c.surname,c.email,group_concat(distinct ce.enforcement)
FROM candidates c
LEFT JOIN candidates_enforcement ce
ON c.id=ce.candidates_id
GROUP BY c.id,c.firstname,c.surname,c.email
The distinct option will help you remove off the redundant values.
As I understood you want both enforcement to show up when a person has two enforcement. The problem here is you are LEFT joining the candidates_enforcement table to candidate table.
LEFT join does is get the tuples from candidate table and joins the corresponding tuples from candidates_enforcement. There is only one tuple for candidate in candidate table. So it only shows up one time whether candidates_enforcement has many tuples for that particular candidate or not.
To correct this do a RIGHT JOIN. Or you can do the same LEFT JOIN, with tables swapped.
SELECT c.id, c.firstname, c.surname, c.email,ce.enforcement
FROM candidates AS c
RIGHT JOIN candidates_enforcement as ce on c.id = ce.candidates_id

inner join on multiple tables, count & distinct

I have 3 tables and I am trying to join those tables with inner join. however when I use count(distinct column_id) it mysql through error which is
SQL syntax : check
for the right syntax to use near '(DISTINCT as_ticket.vehicle_id) FROM as_vehicle INNER JOIN as_ticket
My Query
SELECT
`as_vehicle`.`make`, `as_vehicle`.`model`, `as_odometer`.`value`
COUNT (DISTINCT `as_ticket`.`vehicle_id`)
FROM `as_vehicle`
INNER JOIN `as_ticket`
ON `as_vehicle`.`vehicle_id` = `as_ticket`.`vehicle_id`
INNER JOIN `as_odometer`
ON `as_odometer`.`vehicle_id` = `as_vehicle`.`vehicle_id`
WHERE `as_ticket`.`vehicle_id` = 7
ORDER BY `as_odometer`.`value`
DESC
Tbl as_vehicle
+------------+-------------+---------+
| vehicle_id |make | model |
+------------+-------------+---------|
| 1 | HYUNDAI | SOLARIS |
| 2 | A638EA15 | ACCENT |
+-------------+------------+---------+
Tbl as_odometer;
+------------+-------+
| vehicle_id | value |
+------------+-------+
| 1 | 10500 |
| 5 | 20000 |
| 1 | 20000 |
+------------+-------+
Tbl service
+-----------+------------+
| ticket_id | vehicle_id |
+-----------+------------+
| 1 | 1 |
| 2 | 1 |
+-----------+------------+
You forgot a comma before count.
SELECT `as_vehicle`.`make`, `as_vehicle`.`model`, `as_odometer`.`value`,
count(DISTINCT `as_ticket`.`vehicle_id`) // here ---^
First, you should not have a space after the count() and you have a missing comma (as already noted). More importantly, you don't have a group by, so your query will return one row.
And, because of the where clause, the value will always be "1". You have restricted the query to just one vehicle id.
I suspect the query you want is more like:
SELECT `as_vehicle`.`make`, `as_vehicle`.`model`, `as_odometer`.`value`
COUNT(*)
FROM `as_vehicle` INNER JOIN
`as_ticket`
ON `as_vehicle`.`vehicle_id` = `as_ticket`.`vehicle_id` INNER JOIN
`as_odometer`
ON `as_odometer`.`vehicle_id` = `as_vehicle`.`vehicle_id`
WHERE `as_ticket`.`vehicle_id` = 7
GROUP BY `as_vehicle`.`make`, `as_vehicle`.`model`, `as_odometer`.`value`
ORDER BY `as_odometer`.`value` DESC;
Also, you should learn to use table aliases and all those backquotes don't help the query.