MySQL Joins on n to n relational table keping NULL values - mysql

This is a vote system, where candidate can be voted from different(limited) places. and I want the number of vote per place of each candidate.
I have 3 tables
TABLE candidate
------------------
id
name
TABLE place
------------------
id
label
TABLE vote
------------------
id
id_candidate
id_vote
no_votes // represents the amount of votes in this place for that particular candidate
Suppose I have 10 candidates and 15 different places, I'm trying to make a query that will return 10*15 = 150 rows even if there is no votes, keeping NULL value for ids that are not present in the relational table(which i can replace by 0).
But i'm not making the correct query
Here is the query i made so far (i've tried many modification, inner, outer joins... but nothing worked)
SELECT *
FROM votes
RIGHT JOIN candidate ON candidate.id = candidate_id
LEFT JOIN palce ON place.id = place_id

First, if you want the number of votes per candidate, then you should be thinking "aggregation".
Second, don't mix left and right joins in a query. It is just confusing. Start with the table where you want to keep all the rows, and then just use left join.
So, something like this:
SELECT c.*,
SUM(p.place_name = 'place1') as place1,
SUM(p.place_name = 'place2') as place2,
SUM(p.place_name = 'place3') as place3
FROM candidate c LEFT JOIN
votes v
ON c.id = v.candidate_id LEFT JOIN
place p
ON p.id = v.place_id
GROUP BY c.id;

Considering:
TABLE vote
------------------
id PK
id_candidate FK to candidate
id_vote FK to place
no_votes
-
SELECT CA.name,
PL.label,
SUM(VO.no_votes) as votes
FROM candidate CA
LEFT JOIN vote VO ON CA.id = VO.id_candidate
LEFT JOIN place PL ON PL.id = VO.id_vote
GROUP BY CA.id, PL.id

Related

How to Join three tables properly

The main table has 4 columns:
User Activity Table
userActivityId userId therapistId activityId
1 1 1 1
Each of these columns is a table and these values are all foreign keys.
Basically im trying to run a query that will join to the users table and pull their first and last name based off the user Id.Same thing with therapist - join to the therapist table, pull first + last name.And finally Join to the Activity table and pull the activity name and path from the activity Id
The other tables look like this:
User Table
userId fName lName
Therapist Table
therapistId therapistFirstName therapistLastName
Activity Table
activityId activityTitle activityPath
So far my query looks like
SELECT
User_Activities.userId,
User_Activities.therapistId,
User_Activities.activityId,
Activities.activityTitle,
Activities.activityPath,
Users.fName,
users.lName,
Therapists.therapistFirstName,
Therapists.therapistLastName
FROM
User_Activities
INNER JOIN Users
ON User_Activities.userId = Users.userId
INNER JOIN Therapists ON
User_Activities.therapistId = Therapists.therapistId
INNER JOIN Activities ON
Activities.activityId = User_Activities.userActivityId
WHERE
User_Activities.userId = 1;
When I run this query It only returns 1 row as a result. However there are two activities in the User_Activites table assigned to userId 1.
If I change : INNER JOIN Activities ON
Activities.activityId = User_Activities.userActivityId
from an INNER JOIN to the LEFT JOIN it will display the second row, however the activityTitle and activityPath will be displayed as NULL in the second row.
userActivityId userId therapistId activityId activityId activityTitle activityPath fName lName therapistFirstName therapistLastName
1 1 1 1 1 Brain GZZ0zpUQ S C M D
11 1 1 1 NULL NULL NULL S C M D
You have pretty much answered your question. The second activity does not have a valid ActivityId.
If you want all activities for a user, then you should phrase the query as:
SELECT . . .
FROM Users u LEFT JOIN
User_Activities ua
ON ua.userId = u.userId LEFT JOIN
Therapists t
ON ua.therapistId = t.therapistId LEFT JOIN
Activities a
ON a.activityId = ua.userActivityId
WHERE u.userId = 1;
You want to start with the table where you want to keep all the rows. Then use LEFT JOIN to bring in other tables.
Two other changes of note:
Table aliases are used to simplify reading and writing the query. The SELECT needs to change to use the aliases.
The WHERE clause refers to the Users table rather than UserActivities.

Sql query join on not eqal

So i have this relational model for hospital (not made by me).
Patient (has an adress and an id), hospital (has id and address), and also there's a table for relationship representing placement in the hospital (hospital.id, patient.id) (also there's other tables, but they don't matter in this query);
The purpose of the query is to find hospitals where is no placed patients from from other cities than hospital's one (on condition that address only contains city).
The problem that i have is theoretical, i don't really know if to use full outer join with a or b null, or something else in the query that finds hospitals containing "foreign" patients, (like join hospital with its placement and then full outer join with a or b table record null, but that leads to a question will i get results in the query? Because i need cities that don't match but all the explanations of that join are about .
Thanks to all who embraced my utterly imperfect english and understood it.
Upd.
Patient:
id=1, city =A;
id=2, city =B;
id=3, city =B;
id=4, city =A;
id=5, city =C;
Hospital:
Id =1, city=A
id =2, city=B;
Placement:
h.id p.id
1 1
1 4
2 2
2 3
2 5
Expected results is "1", id of the first hospital (where's no patients from other city) and others with that "feature"
my query is like
select id from hospital where id not in
(select id,address from hospital inner join placement on h.id=placement.h.id as b inner join patient on placement.p.id=p.id where hospital.address<>patient.address )
Sorry for the delay
Is shawn's query correct?
Can i use h.id instead 1? Idk if our teacher would accept that, because he's never showed us something like that and in 10 years he hasn't managed to create an example of that database for students to test queries on.
select * from hospitals h
where not exists (
select 1 -- dummy value, use h.id if you prefer
from patients p inner join placement pl on pl.pid = p.id
where pl.hid = h.id and p.city <> h.city
)
or
select h.id
from hospitals h
left outer join
placement pl inner join patients p on p.id = pl.pid
on pl.hid = h.id
group by h.id
-- this won't count nulls resulting from zero placements for that hospital
-- as long as standard sql null comparisons are active
having count(case when h.city <> p.city then 1 end) = 0
Looks like it works to me: http://rextester.com/BTJB59061

Select data based on another table

I have three tables, I'll just list the important columns
db_players
id | name
players
id | teamid | careerid
db_teams
The db_teams id links to the players teamid.
I need to run a query where I select rows from db_players as long as db_teams.id isn't in a row in players as teamid where the careerid = 1.
I've never attempted this type of query with mysql before, I know I could do two queries and involve php but I'm intrigued as to whether it's possible with a pure db query.
Thanks.
EDIT - simpler now.
SELECT dp.first_name
FROM tbl_foot_career_db_players dp
INNER JOIN tbl_foot_career_players p
ON p.playerid != dp.id
WHERE p.careerid = 1
The idea is that I want to return all rows from tbl_foot_career_db_players WHERE the id from that table isn't present in a row in tbl_foot_career_players in the column playerid. And the tbl_foot_career_players.careerid must also equal 1.
List all db_players that are not in players with career = 1
SELECT d.*
FROM db_players d
LEFT JOIN players p
ON p.player_id = d.id
AND p.career = 1
WHERE p.id IS NULL
Try to JOIN them with db_players id is not in players teamid.
SELECT db_players.* FROM db_players
LEFT JOIN players ON players.id != db_players.id
LEFT JOIN db_teams ON players.teamid = db_teams.id
WHERE careerid = 1

SQL Join and show results if join false

I have a table - comments. Users can post if not a member of the site but want to show their details if they are.
So if a user comments who is NOT a member I show their posts but don't link to their profile, because they don't have one.
So, in the following query I want to return the rows even if there is no join:
select wc.comment, wc.comment_by_name, wc.user_id, u.url from comments wc
join users u on wc.wag_uid = u.user_id
where id = '1237' group by wc.comment order by wc.dateadded desc
I want to return:
comment comment_by_name user_id url
------- --------------- ------- ----
hello dan 12 /dan
hey jane /jane
world jack 10 /jack
But the above does not return the data for jane as she does not have a user_id
Is there a way to return all data even if the join is null?
use LEFT JOIN instead
SELECT wc.comment, wc.comment_by_name, wc.user_id, u.url
FROM comments wc
LEFT JOIN users u
on wc.wag_uid = u.user_id
WHERE id = '1237'
GROUP BY wc.comment
ORDER BY wc.dateadded DESC
basically INNER JOIN only select records which a record from one table has atleast one match on the other table while LEFT JOIN select all rows from the left hand side table (in your case, it's comments) whether it has no match on the other table.

Mysql query in drupal database - groupwise maximum with duplicate data

I'm working on a mysql query in a Drupal database that pulls together users and two different cck content types. I know people ask for help with groupwise maximum queries all the time... I've done my best but I need help.
This is what I have so far:
# the artists
SELECT
users.uid,
users.name AS username,
n1.title AS artist_name
FROM users
LEFT JOIN users_roles ur
ON users.uid=ur.uid
INNER JOIN role r
ON ur.rid=r.rid
AND r.name='artist'
LEFT JOIN node n1
ON n1.uid = users.uid
AND n1.type = 'submission'
WHERE users.status = 1
ORDER BY users.name;
This gives me data that looks like:
uid username artist_name
1 foo Joe the Plumber
2 bar Jane Doe
3 baz The Tooth Fairy
Also, I've got this query:
# artwork
SELECT
n.nid,
n.uid,
a.field_order_value
FROM node n
LEFT JOIN content_type_artwork a
ON n.nid = a.nid
WHERE n.type = 'artwork'
ORDER BY n.uid, a.field_order_value;
Which gives me data like this:
nid uid field_order_value
1 1 1
2 1 3
3 1 2
4 2 NULL
5 3 1
6 3 1
Additional relevant info:
nid is the primary key for an Artwork
every Artist has one or more Artworks
valid data for field_order_value is NULL, 1, 2, 3, or 4
field_order_value is not necessarily unique per Artist - an Artist could have 4 Artworks all with field_order_value = 1.
What I want is the row with the minimum field_order_value from my second query joined with the artist information from the first query. In cases where the field_order_value is not valuable information (either because the Artist has used duplicate values among their Artworks or left that field NULL), I would like the row with the minimum nid from the second query.
The Solution
Using divide and conquer as a strategy and mysql views as a technique, and referencing this article about groupwise maximum queries, I solved my problem.
Create the View
# artists and artworks all in one table
CREATE VIEW artists_artwork AS
SELECT
users.uid,
users.name AS artist,
COALESCE(n1.title, 'Not Yet Entered') AS artist_name,
n2.nid,
a.field_image_fid,
COALESCE(a.field_order_value, 1) AS field_order_value
FROM users
LEFT JOIN users_roles ur
ON users.uid=ur.uid
INNER JOIN role r
ON ur.rid=r.rid
AND r.name='artist'
LEFT JOIN node n1
ON n1.uid = users.uid
AND n1.type = 'submission'
LEFT JOIN node n2
ON n2.uid = users.uid
AND n2.type = 'artwork'
LEFT JOIN content_type_artwork a ON n2.nid = a.nid
WHERE users.status = 1;
Query the View
SELECT
a2.uid,
a2.artist,
a2.artist_name,
a2.nid,
a2.field_image_fid,
a2.field_order_value
FROM (
SELECT
uid,
MIN(field_order_value) AS field_order_value
FROM artists_artwork
GROUP BY uid
) a1
JOIN artists_artwork a2
ON a2.nid = (
SELECT
nid
FROM artists_artwork a
WHERE a.uid = a1.uid
AND a.field_order_value = a1.field_order_value
ORDER BY
uid ASC, field_order_value ASC, nid ASC
LIMIT 1
)
ORDER BY artist;
A simple solution to this can be to create views in your database that can then be joined together. This is especially useful if you often want to see the intermediate data in the same way in some other place. While it is possible to mash together the one huge query, I just take the divide and conquer approach sometimes.