SQL Query to match unlinked data - mysql

Say I have three tables:
TABLE A
idA variable
1 Number of hats
2 Number of scarves
3 Number of mittens
TABLE B
idB name
1 Andy
2 Betty
3 Cedric
4 Daphne
TABLE C
idA idB value
1 1 15
1 2 2
1 3 89
2 1 10
2 3 3
2 4 1504
3 2 12
3 3 4
3 4 1
Looking at the table, it's relatively simple to work out - we know how many hats (2) and mittens (12) that she owns, but not how many scarves. Likewise, for Daphne we know how many scarves (1504) and mittens (1) she owns, but not the amount of hats.
However, I'd like a list of fields that there ISN'T information for - I would have a returned result looking something like this (if I asked for Andy)
idA variable
3 Number of mittens
Any idea how I do that? :)

The following query works:
SELECT B.name, A.variable
FROM B
CROSS JOIN A
LEFT JOIN C ON C.idA = A.idA AND C.idB = B.idB
WHERE C.value IS NULL
Its the CROSS JOIN that is key, it says JOIN every record in B to every record in A. Once you've done that you can easily check which combinations of idA and idB don't have a corresponding record in C.
Tested on SQLFiddle
Result:
NAME UNKNOWN VARIABLE
-------------------------------
Andy Number of mittens
Betty Number of scarves
Daphne Number of hats

You can use joins to associate 2 tables.
In your case, if you ask for Andy and you wanna know the number of mittens, you'll have:
SELECT name, value
FROM B
INNER JOIN C on B.idB = C.idB
WHERE id.A = 3
Responding to your comment, you try something like that:
SELECT name, variable
FROM B
RIGHT JOIN C on B.idB = C.idB
RIGHT JOIN A on C.idA = A.idA
WHERE C.idA IS NULL

select idA, variable
from a
where idA not in (select idA from c where idB = 1)

Related

join to most recent right hand record, but place NULL when right table doesn't have data

i am trying to build a view/query that hops across multiple joins showing one result row per left hand record, with the right hand record being the most recently-created record for that object in the table.
the end goal is to provide data to a front end application, but i need to be able to tell if there is no data in the right hand table while still displaying the data from the left hand table.
i don't know if i've worded that well; i'm a little out of my depth. maybe code will illustrate better than english. here are some tables to illustrate:
left hand table bill
id description extId
1 Some descr... SB 123
2 Another de... SB 124
3 Third desc... SB 125
join table tally_bill
id billId tallyId
1 2 1
2 2 2
3 3 3
4 3 4
note that there is no entry for billId = 1
right hand table tally
id countYes countNo created
1 4 0 2022-09-26 13:11:48
2 5 8 2022-09-26 14:50:24
3 10 11 2022-09-26 11:20:01
4 4 3 2022-09-26 13:41:25
my desired result looks something like this:
billId description extId countYes countNo
1 Some descr... SB 123 null null
2 Another de... SB 124 5 8
3 Third desc... SB 125 4 3
i need countYes and countNo to be the values from the most recently-created record in tally.
the query i've gotten so far is:
SELECT
bill.id, bill.description, bill.extId,
tally.id, tally.countYes, tally.countNo
FROM
bill
LEFT JOIN tally_bill
ON tally_bill.billId = bill.id
LEFT JOIN tally
ON tally.id = tb.tallyId
AND (
SELECT
MAX(tally.id)
FROM
tally_bill tb
WHERE
tb.billId = bill.id);
but this produces something to the effect of:
billId description extId id countYes countNo
2 Another de... SB 124 2 5 8
3 Third desc... SB 125 4 4 3
as you see the record billId = 1 is missing. i have made a few variations on this query moving the subquery around, trying group bys and partitions, etc., to no avail. i'm at the limit of my SQL-fu, so i would appreciate any help or enlightenment, please :)
i'm using MySQL 8.0.30 but if there is a need to change versions i'm open to it.
select id
,description
,extId
,countYes
,countNo
from (
select b.id
,b.description
,b.extId
,t.countYes
,t.countNo
,row_number() over(partition by b.id order by t.id desc) as rn
from bill b left join tally_bill tb on tb.billId = b.id left join tally t on t.id = tb.tallyId
) t
where rn = 1
id
description
extId
countYes
countNo
1
Some descr
SB 123
null
null
2
Another de
SB 124
5
8
3
Third desc
SB 125
9
4
Fiddle

How can i count all rows without miss them?

SELECT t1.s_name, count(*) FROM tvSeries AS t1, subTitles AS t2, votes as t3
WHERE
t1.s_id IN (SELECT t2.s_id WHERE sLang='English') AND
t1.s_id IN (SELECT t3.s_id WHERE pts=5) AND
t1.s_id IN (SELECT t3.s_id WHERE uid='britney');
My tvSeries table is like:
s_id s_cat s_name
1 comedy a
2 comedy b
3 drama c
4 comedy d
5 drama e
My subTitles table is like:
s_id sLang
1 English
1 Spanish
2 French
2 English
3 English
1 French
4 German
4 English
5 English
My votes table is like:
s_id uid pts
1 john 4
1 mia 3
1 britney 5
2 rock 5
3 anna 1
3 britney 5
4 megan 3
5 britney 5
I want to select total number of tvSeries and name of tvSeries in this conditions;
which tvSeries gets 5 star from user 'britney' with English subtitles.
When I use my code, I get only one row with number of tvSeries but i want to see many rows with total value. Can anyone help me?
You can do this with simple JOINs (see this answer for an explanation of JOIN vs ,), and then your conditionals are clean and easy to understand.
SELECT
t.s_id,
t.s_name
FROM
tvSeries t
JOIN subTitles s ON s.s_id = t.s_id
JOIN votes v ON v.s_id = t.s_id
WHERE
s.sLang = 'English'
AND v.pts = 5
AND v.uid = 'britney';
If you want just the count of shows instead, you can do:
SELECT
COUNT(*) as count
FROM
...
You can't easily get both the names of the series as well as the count in the same row (because COUNT is an aggregating function), but if you need it you can do:
SELECT
GROUP_CONCAT(t.s_name) as series_names,
COUNT(*) as count
FROM
...
though that returns a single row with concatenated series names (a,c,e) rather than rows which are able to be iterated over.
See http://sqlfiddle.com/#!9/2c252c/13 for a working example.

How to create a new column in output and give values using join in MySQL

I have two tables 'group' and 'prize' where I have id, mem_id, membername in group table and id, prize_memid, prized_memname in prize table.
***group table***
id mem_id membername
1 1 A
2 1 A
3 2 B
4 3 C
5 3 C
***prize table***
id prize_memid prized_memname
1 1 A
2 32 yy
3 20 ww
4 2 B
I want my result like this using JOINS (membership is extra column, that is not in table)
***Output***
mem_id membername membership
1 A Prized
2 B Prized
3 c Non-Prized
Group By on mem_id and member_name. Do Left join starting from group table to prize table on mem_id. Left join will allow us to consider all the members, whether they have been given a prize or not.
Note that group is a Reserved keyword in MySQL. You should really considering renaming your table to something else. Otherwise, you will have to use backticks around it.
Now, use Conditional functions like If() to check if a particular has prize or not (COUNT(prize_memid) should be greater than zero, if he has been given a prize atleast once).
Try the following:
SELECT
g.mem_id,
g.membername,
IF(COUNT(p.prize_memid) > 0, 'Prized', 'Non-Prized') AS membership
FROM
`group` as g
LEFT JOIN `prize` as p ON p.prize_memid = g.mem_id
GROUP BY g.mem_id, g.membername

MySQL query table filtering issue

I've been struggling on the following.
I have 3 tables: players, players_clothes, and teams_clothes.
Table players:
id user team_id
1 tom 4
2 robo 5
3 bob 4
So tom and bob are both on the same team
Table players_clothes:
id clothes_id p_id
1 13 1
2 35 3
3 45 3
Bob has clothing article 35 and 45, robo has none.
Table teams_clothes:
id clothes_id team_id
1 35 4
2 45 4
3 55 4
4 65 5
This shows which teams have rights to which articles of clothing. The problem: tom is wearing an article of clothing that does no belong to his team... Let's assume this is illegal.
I'm having trouble figuring out how to capture all those who are wearing illegal clothes for a particular team.
SELECT pc.clothes_id FROM players AS p
JOIN players_clothes AS pc
ON p.id = pc.p_id
AND p.team_id = 4 GROUP BY pc.clothes_id
(I group by players_clothes.clothes_id because believe it or not, two players can be assigned the same piece of clothing)
I think this results the following set (13, 35, 45)
Now I would like to check against the actual set of clothes that team 4 owns.
SELECT clothes_id FROM teams_clothes WHERE team_id = 4 and this return (35, 45, 55)
How can I create a query so that it returns (13)? I've tried things like NOT EXISTS IN but I think the GROUP BY players_clothes.clothes_id part gets in the way
I suggest
select * from A where team_id = $team_id join B on B.a_id = A.id
where not exists
(
select 1 from C where C.clothes_id = B.clothes_id and team_id = $team_id
)
Basically, we find all As who are on their team and for each A join to all clothing they wear, and then only return the row IF we can't find indication in table C that the clothing is on our team (this covers not existent in C and exists but in the wrong team on C)
This should do the trick:
SELECT b.a_id, b.clothes_id
FROM
b INNER JOIN a
ON b.a_id = a.id
LEFT OUTER JOIN c
ON a.team_id = c.team_id
WHERE
c.clothes_id = NULL
The thought is to do an outer join on the combination of tables A/B against table C. And then only look for the cases where c.clothes_id is NULL, which would represent those cases where there is no relational match on the outer join (i.e. the clothes item is not approved for that user's team).
Not sure if this is too late for you, but I'd change the database model itself to make this situation impossible in the first place:
("Unimportant" fields omitted for brevity, including surrogate keys such as PLAYER_ID.)
Note how TEAM_ID migrates through the identifying relationship from TEAM to PLAYER, and then to the PLAYER_ARTICLE, where it merges with the same field migrated through the TEAM_ARTICLE. Since there is only one physical TEAM_ID field in the PLAYER_ARTICLE table, you can never insert a row that would reference different teams.
To put it in more abstract terms: this is a diamond-shaped dependency, where TEAM is at the top and PLAYER_ARTICLE at the bottom of the diamond. The merger at the bottom (enabled by the usage of identifying relationships) ensures sides must always point to the same top.
Your example data would be represented like this...
PLAYER:
TEAM_ID PLAYER_NO
4 1 -- Tom
5 1 -- Robo
4 2 -- Bob
TEAM_ATRICLE:
TEAM_ID ARTICLE_ID
4 35
4 45
4 55
5 65
PLAYER_ARTICLE:
TEAM_ID PLAYER_NO ATRICLE_ID
4 1 13 -- Tom: this is impossible (FK violation).
4 2 35 -- Bob
4 2 45 -- Bob

MySQL Join Multiple (More than 2) Tables with Conditions

Assume I have 4 tables:
Table 1: Task
ID Task Schedule
1 Cut Grass Mon
2 Sweep Floor Fri
3 Wash Dishes Fri
Table 2: Assigned
ID TaskID (FK) PersonID (FK)
1 1 1
2 1 2
3 2 3
4 3 2
Table 3: Person
ID Name
1 Tom
2 Dick
3 Harry
Table 4: Mobile
ID PersonID (FK) CountryCode MobileNumber
1 1 1 555-555-5555
2 2 44 555-555-1234
3 3 81 555-555-5678
4 3 81 555-555-0000
I'm trying to display the
Task on a certain day
Name of person assigned to task
Phone numbers of said person
I think it should be something like the following, but I'm not sure how to set up the conditions so that the results are limited correctly:
SELECT T.ID, T.Task, P.Name, M.MobileNumber
FROM Task AS T
LEFT JOIN Assigned AS A
ON T.ID = A.TaskID
LEFT JOIN Person AS P
ON A.PersonID = P.ID
LEFT JOIN Mobile AS M
ON M.PersonID = P.ID
WHERE T.Schedule = Fri
My goal is to fetch the following information (it will be displayed differently):
Tasks Name MobileNumber
Sweep Floor, Wash Dishes Dick, Harry 44-555-555-1234, 81-555-555-5678, 81-555-555-0000
Of course, if JOIN is the wrong way to do this, please say so.
It's unclear what you want to do with duplicate data in this case, but you should be looking at using inner joins instead of outer joins, and using something like group_concat() to combine the phone numbers.