I have data related as follows:
A table of Houses
A table of Boxes (with an FK back into Houses)
A table of Things_in_boxes (with an FK back to Boxes)
A table of Owners (with an FK back into Houses)
In a nutshell, a House has many Boxes, and each Box has many Things in it. In addition, each House has many Owners.
If I know two Owners (say Peter and Paul), how can I list all the Things that are in the Boxes that are in the Houses owned by these guys?
Also, I'd like to master this SQL stuff. Can anyone recommend a good book/resource? (I'm using MySQL).
Thanks!
Peter and Paul are gay couple ?
Then you should go for many-to-many relationship instead of having ownerID inside of Houses Table
ie. Houses2Owners with two columns ownerID and houseID
then the query would be
select item from houses as h
left join Boxes as b on h.houseID=b.houseID
left join Things as t on b.boxID=t.boxID
left join Houses2Owners as h2o on h.houseID=h2o.houseID
left join Owners as o on h2o.ownerID=o.ownerID
Main question you should ask yourself while designing that would be whether each object will appear once ie. if there are two similar boxes with similar things in them or ie. two boxes with ski masks in them.
Then you should create tables with no relationship to parent object and also to create a table that connects two tables. This way you will avoid ski mask to appear twice for two boxes which contain that mask.
SELECT
Things_in_boxes.*
FROM
Houses
JOIN Boxes ON Houses.HouseID = Boxes.House
JOIN Things_in_boxes ON Boxes.BoxID = Things_in_boxes.Box
WHERE
Houses.Owner = 'Peter' OR Houses.Owner = 'Paul'
As for resources to learn from... I can't really suggest anything specific. I learnt how to use (My)SQL gradually and from a number of sources, and can't single any of them out as having been of primary importance. w3schools has OK coverage of the very basic stuff, and MySQL's own documentation (available on the web, google for it) does an OK job and is a reasonable reference for when you want to know the nitty gritty of some topic or other.
EDIT: The above answer is wrong. I had missed the stipulation that a House can have multiple Owners.
New approach: I'll assume that there is a cross-referencing table, HouseOwners, with House and Owner as foreign keys.
My first thought was this:
SELECT
Things_in_boxes.*
FROM
Houses
JOIN Boxes ON Houses.HouseID = Boxes.House
JOIN Things_in_boxes ON Boxes.BoxID = Things_in_boxes.Box
JOIN HouseOwners ON Houses.HouseID = HouseOwners.House
WHERE
HouseOwners.Owner = 'Peter' OR HouseOwners.Owner = 'Paul'
However, this is not quite right. If both Peter and Paul are Owners of a given house, then the things in the boxes in that house would show up twice. I think a subquery is needed.
SELECT
Things_in_boxes.*
FROM
Houses
JOIN Boxes ON Houses.HouseID = Boxes.House
JOIN Things_in_boxes ON Boxes.BoxID = Things_in_boxes.Box
WHERE
Houses.HouseID IN (
SELECT DISTINCT House
FROM HouseOwners
WHERE Owner = 'Peter' OR Owner = 'Paul'
) AS MySubquery
SELECT t.name
FROM Houses h
INNER JOIN Boxes b ON b.houseId = h.id
INNER JOIN Things t ON t.boxId = b.id
INNER JOIN Owners o ON o.houseId = h.id
WHERE o.name = 'Peter' OR o.name = 'Paul'
By using inner joins you can combine these 4 tables with all the linked information. There is also an other way using inner select queries:
SELECT t.name
FROM Houses h
INNER JOIN Boxes b ON b.houseId = h.id
INNER JOIN Things t ON t.boxId = b.id
INNER JOIN Owners o ON o.houseId = h.id
WHERE h.id IN (SELECT o.housId
FROM Owners o
WHERE o.name = 'Peter' OR o.name = 'Paul')
This query works differently (by first finding the two house ID's of Peter and Paul and then performing the join), but it has the same effect.
Hopefully these examples will help you understand SQL :)
This isn't tested and written on the spot:
SELECT *
FROM
`things_in_boxes` AS a
LEFT JOIN `houses` AS b
on ( a.`house_id` = b.`house_id` )
LEFT JOIN `owners` AS c
on ( b.`house_id` = c.`house_id` )
WHERE c.`owner_id` IN( 0, 1 )
That is the general structure I would use, where the "0, 1" in the last IN statement are the owner ids for Peter and Paul. If you wanted to do it by name, you could simply make it something like
c.`name` IN( 'Peter', 'Paul' )
As far as books, I can't really tell you, I've learned through tutorials and references.
Here's one approach:
SELECT * FROM Things_in_boxes t
WHERE box_id IN (
SELECT b.id
FROM Boxes b
INNER JOIN Owners o
ON (o.house_id = b.house_id)
WHERE o.name LIKE 'Peter'
OR o.name LIKE 'Paul'
)
Note that you don't need to join on the House table, since both the Boxes and Owners have a house id.
Without knowing the full structure, I will assume a structure to build a query, step by step
Get the IDs of the houses belonging to the owners
select id from House where owner in ('peter', 'paul')
Get the boxes in those houses
select boxid from boxes where homeid in (select id from House where owner in ('peter', 'paul'))
Get the things in those boxes
select * from things where boxid in (select boxid from boxes where homeid in (select id from House where owner in ('peter', 'paul')))
This should get you what you want, but is very inefficient.
In the above method, The final query in step 3 gets the ids in each step, and stores them in temporary storage while it consumes them. This is a very slow operation in most DBMS.
The better alternative is a join. Combine all the tables and select the desired data.
select * from things join boxes on things.boxid =boxes.boxid join houses on boxes.houseid=house.id join owners on houses.owner=owner.ownerid where owner.name in ('peter',''paul)
Related
I am newish to SQL and Join statements and I am way out of my league at the moment
I currently have 6 Database Tables that are all linked to the main 7th table based on the main tables ID, however all the information in the other 6 tables are looped and so have several displayed results to the one main tables ID.
Is it possible to join them all into one Join Statement so I can have a results so that everyones information from the main table also shows their information from the 6 other linked tables
So basically when they all have the informationed joined I want to be able to Display all information on a webpage
so I was wondering do I need to do multiple JOIN statements or just one Longer one?
I have Included some Images below that explain it visually. See examples 1 and 2
The columns that are highlighted in yellow are looped to have many results:
2. This is the example of how the information is looped into the
database where there are many Race_id sharing to the same inf_id:
Im not so sure how it will look once it has been joined since some of the information is looped into many Id's and not sure if that means it need to duplicate the column or the rows?? any help would be greatly appreciated.
You could use left join eg for the first tables influencer, social, activities
select i.*, s.follower, s.Social_Medial_URL, a.activity, a.result
from influencer i
left join social s on s.inf_id = i.id
left join activities a on a.inf_id = i.id
you can procede yourself adding the left join for the others tables using the same rules
select i.*
, s.follower_count
, s.social_media_url
, a.compete_activity
, a.compete_results
from influencers i
left join inf_other_social s on s.inf_id = i.id
left join inf_compete_activity a on a.inf_id = i.id
LIMIT 0, 25
I think you are confused about primary key and foreign key. the picture you have given is clearly elaborate everything. as per your db diagram the query might be like this...
select * from
Influencer i
Left Join Social s on i.inf_id = s.inf_id
Left Join Owned_Equip o on i.inf_id=o.inf_id
Left Join Ages_Interacted a on i.inf_id = a.inf_id
Left Join Activities ac on i.inf_id = ac.inf_id
Left Join Awards aw on i.inf_id = aw.inf_id
Left Join History h on i.inf_id = h.inf_id
By using this above query you can get all the information of Influencer and related to him (Social,Owned_Equip,Activities,Award etc) whether they exists or not. If you using only "Join" not "Left Join" then you can only find those records which is common for a single influencer to it's related entities/tables which might you say. as an example: say Influencer (id = 1 , suppose name is dan) after inner join we can get only records related to dan ( his social,owned equipments,activites,awards and so on if those tables contains record related to dan record)
I have a slightly complex table structure that I'm trying to query for a search function, but my queries keep timing out. Basically, it's a book search, and I'm focusing on the subject portion of that search.
The subjects table is simple (id and title), but there's a link table that refers it back to itself called subjects_subjects, which complicates things.
**subjects_subjects**
id (key)
subject_id (reference to subjects table)
see_subject_id (another reference to subjects table)
The reason for the looping reference is to catch subjects that don't contain any books, but point to subjects that do. For example, there's no books under the 'Travel' subject, so that subject has a link to 'Explorers' and 'Voyages' that do contain books. The point is to make searching easier.
So what I'm trying to do is allow the user to search for 'Travel', but return results from 'Explorers' and 'Voyages'. Here's my query that times out:
SELECT
BK.id,
BK.title
FROM
books BK
LEFT OUTER JOIN
books_subjects BS
ON BS.book_id = BK.id
WHERE
BS.subject_id IN (1639,3173)
OR BS.subject_id IN
(
SELECT
SS.see_subject_id
FROM
subjects_subjects SS
WHERE
SS.subject_id IN (1639,3173)
)
GROUP BY
BK.books_id
Extra info: There are 17000 books and over 3000 subjects in the database, with roughly 84000 book/subject references.
Can anyone help me figure out where am I going wrong here?
You're doing two things that MySQL optimizes poorly:
OR in the WHERE clause.
IN (SELECT ...)
Instead of OR, use two queries that you combine with OR. And instead of IN (SELECT ...) use a JOIN.
Also, you shouldn't use LEFT JOIN if you don't need to return rows from the first table with no matches in the second table, use INNER JOIN.
SELECT b.id, b.title
FROM books AS b
JOIN books_subjects AS bs ON bs.book_id = b.id
WHERE bs.subject_id IN (1639, 3173)
UNION
SELECT books AS b
JOIN books_subjects AS bs ON bs.book_id = b.id
JOIN subjects_subjects AS ss ON bs.subject_id = ss.see_subject_id
WHERE ss.subject_id IN (1639, 3173)
So, I'm working on my outer join skills with pure SQL (MySQL is the DBMS I'm using). I've got four tables: People, Address, Phone, Email. All are pretty self explanatory. People is the main table, every record in the other three contains a foreign key references the People table.
I want to write a query that will do outer joins on all four tables. Ideally, here is what I want the results to look like:
Name Address Phone Email
Bob 5 Steel Dr 512-222-1358 bob#gmail.com
Bob 212-333-4444 bob#aol.com
Bob bob#hotmail.com
The idea is to have the address/phone/email records not repeat at all. The info from the People table can repeat, that's fine, but I would be trying to avoid repeats from the others.
Here is a very, very crude version of what I'm going for.
select p.name, e.email, a.address
from people p
left join email e on p.pid = e.pid
left join address a on p.pid = a.pid
where p.pid = 1;
It doesn't work, though. It repeats all the records that match each other.
Is there any way to get the effect I'm going for? I'm a bit rusty on outer joins.
Not without jumping through a lot of unreliable "hoops", usually involving session variables.
You are overlooking that the result set you want implies relationships that are just not there. 5 Steel Dr, 512-222-1358, and bob#gmail.com have no direct connection. Your results slightly imply the phone number may be present at the address. What you have are 3 lists that happen to be aligned side by side.
A query that outputs this kind of result is the equivalent of giving each row a "line number".
There is no simple solution to get the output like you describe in a single SQL query.
Which shouldn't be surprising, because that output violates Fourth Normal Form.
Honestly, I would run three queries, one for People joined to Address, one for People joined to Phone, and one for People joined to Email.
select p.name, a.address
from people p
inner join address a on p.pid = a.pid
where p.pid = 1;
select p.name, ph.phone
from people p
inner join phone ph on p.pid = ph.pid
where p.pid = 1;
select p.name, e.email
from people p
inner join email e on p.pid = e.pid
where p.pid = 1;
Then combine the results however you want in application code.
I'm sure this has been asked before, and I apologize for asking it again, but I've done some searching probably for the wrong terms and just haven't been able to find the right approach.
I have two tables. Websites is a list of web sites (id | website), cdd is a list of users and the site they were referred from (userid | website | etc..). Not every site that referred users is a sponsor, and thus in the websites table. Also, not every site that is a sponsor has sent us users. I need a list of the number of users from each sponsor, including the 0s.
This is the query I have so far:
SELECT w.website, COUNT(*) AS num FROM websites w LEFT JOIN cdd c ON w.website = c.website WHERE c.submitted LIKE '05/26/11 %' GROUP BY w.website ORDER BY num DESC;
There are five sites in the website table, but one has not sent any users. That one does not show up in the queries.
Any thoughts?
The LEFT JOIN is correct, but because you are specifying a WHERE clause against the table (cdd) on the right side of the join, you are filtering out websites that have no associated cdd record. You need to specify that criterion like so:
[...]
FROM websites w
LEFT JOIN cdd c ON w.website = c.website
WHERE c.submitted IS NULL OR c.submitted LIKE '05/26/11 %'
[...]
which includes the websites that don't join to any cdd record, or
[...]
FROM websites w
LEFT JOIN cdd c ON w.website = c.website AND c.submitted LIKE '05/26/11 %' 'Replaces WHERE clause
[...]
which includes all websites, but only joins to cdds with the matching submitted date.
Note: To ensure that sites with no associated users return a count of 0, you may also need to COUNT() a column from cdd, rather than *...
I need a list of the number of users from each sponsor, including the 0s.
In that case you probably should be using a left join:
SELECT a.site, COUNT(b.ref_site) AS num FROM table1 a LEFT JOIN table2 b ON a.site = b.ref_site GROUP BY a.site;
Make the INNER JOIN a LEFT JOIN instead and I think you're good to go.
SELECT a.site, COUNT(b.ref_site) AS num
FROM table1 a
LEFT JOIN table2 b
ON a.site = b.ref_site
GROUP BY a.site;
In my database I have cars and persons.
Between the cars and the persons is an n-to-n 'drives'-'is driven by' relationship.
I would like to query the database to get a list of all the cars that are driven by a specific person (say 'john') and that are also driven by other persons. I would like the query to show per car how many (other) persons are driving it.
The above is a simplification of my actual problem. For extra reference;
Cars are typically driven by 1 to 4 persons, but 1 person drives up to 5000 cars
Is it possible to do this in a single query, and if so, how?
greetings,
Coen
Does something like this do the trick?
SELECT p.PersonName, COUNT(d2.PersonId)
FROM Drives d
INNER JOIN
Person p
ON d.PersonId = p.PersonId
LEFT JOIN
Drives d2
ON d.CarId = d2.CarId
AND d.PersonId != d2.PersonId
GROUP BY p.PersonName
To restrict to only cars that are drive by other people, change LEFT JOIN to INNER JOIN.