MySQL Join with looped reference query - mysql

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)

Related

How to do SQL Join with many tables (FK tables have looped results sharing ID)

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)

MySQL - More Join operators, more aliases and Error 1066

Hopefully you are going to help me again :)
Well, the problem that I have is similar to the one which I've posted yesterday, but it is extended.
We are given three tables:
Pfleger
Station
Mitarbeiter
As states above, this problem is very similar to this problem.
What was the result? Well, I get back a table with the ID's and names of the workers who are living in Frankfurt.
Now I should additionally get back the ID'S and names of the workers who are living in Frankfurt AND working in the station called Onkologie.
How should I do this?
My code so far:
SELECT pfleger.PNR, Name
from mitarbeiter, ...
JOIN pfleger on (mitarbeiter.PNR=pfleger.PNR)
JOIN ...
where Ort='Frankfurt' and Name='Onkologie'
I don't know how to make 2nd JOIN.
You could try something like this
select m.PNR, m.Name
from Mitarbeiter m
inner join Station s on s.PNR = m.PNR
inner join Pfleger p on p.StationID = s.StationID
where
m.Ort = 'Frankfurt'
and p.Name = 'Onkologie'
Updated based on provided table names and column names for tables.
Notice: we eliminated the , notation after mitarbeiter, in your base query.
I don't see PNR in mitabeiter so I'm assuming stationID is how they join.
SELECT *
FROM pfleger P
INNER JOIN Station S
on S.StationId = P.StationID
INNER JOIN mitarbeiter M
on M.pnr = S.pnr
WHERE M.ORT='Frankfurt' and P.name = 'Onkologie'
Assumptions I made:
pfleger.stationID has a foreign key relationship to station.stationID
mitarbeiter.PNR has a foreign key relationship to station.PNR
We used inner joins here as we only want Mitarbeiter who exist in all 3 tables.
Otherwise we could use outer joins and return those who don't have records as well. (meaning it is unknown where they work it MIGHT be 'Onkologie' but we don't know as there is no record.)

MySQL: how to get result from 2 tables without repeating results?

I've got 3 tables: book, publisher, book_category
For a particular book category (fantasy) I have to display list of publisher names supplying that genre.
publisher_name and category_name are linked through book table, so my query is:
SELECT publisher.publisher_name
FROM publisher, book, book_category
WHERE publisher.publisher_id = book.publisher_id
AND book.category_id = book_category.category_id
AND category_name = 'fantasy';
But the result I'm getting is repeating the name of publisher if there's more than one fantasy book supplied by that publisher.
Let's say I've got The Hobbit and The Lord of the Rings,both are fantasy and are supplied by the same PublisherA.
In that case the result of my query is:
PublisherA
PublisherA
Is it possible to get that result just once? Even if there's much more than 2 fantasy books
published by the same publisher?
Just use distinct if you only need publisher_name
SELECT distinct publisher.publisher_name
by the way, try to use JOIN syntax... to join tables
SELECT distinct p.publisher_name
FROM publisher p
join book b on b.publisher_id = p.publisher_id
join book_Category bc on bc.category_id = b.category_id
where bc.category_name = 'fantasy'
Use DISTINCT
SELECT DISTINCT publisher.publisher_name
FROM publisher, book, book_category
WHERE publisher.publisher_id = book.publisher_id
AND book.category_id = book_category.category_id
AND category_name = 'fantasy';
Try adding this to the end of the query: GROUP BY publisher.publisher_name
Everyone is mentioning DISTINCT, which is correct (better than GROUP BY in MySQL, because of the way the optimizer is set up), but I figured I would also add a modification for performance enhancements.
Currently you have implicit cross joins to get to the other tables, and making these explicit INNER JOINs will increase efficiency because of the order of filtering. Example:
SELECT DISTINCT Publisher.publisher_name
FROM publisher Publisher
INNER JOIN book Book ON Publisher.publisher_id = Book.publisher_id
INNER JOIN book_category Book_Category ON Book.category_id = Book_Category.category_id
WHERE Book_Category.category_name = 'fantasy';
In the original query, you bring in the complete record set of all three tables (publisher, book, book_category), and then from that set you join on the respective keys, and then return the result set. In this new query, your join to Book_Category happens based only upon the record set returned from the join between Publisher and Book. If there is filtering that happens based on this join, you will see a performance increase.
You also have the added benefit of being ANSI-compliant, as well as explicit coding to improve ease of maintenance.

How to match this using SQL?

I am using the 3 following tables:
First table
id
response
Second table
responseid
patientid
Third table
patientid
The relationship between first and second table is on id and responceid.
The relationship between third and second is on patientid.
Now I need to retrieve values from these tables like all values from first and third tables with the help of matching with patientid from second and 3rd table.
How can I do this?
Basically if all of the columns that defines their relationship are not nullable, then INNER JOIN will suffice. But if they are nullable and you still want to display all records from firstTB, you need to use LEFT JOIN instead of INNER JOIN.
SELECT a.*, b.*, c.*
FROM firstTB a
INNER JOIN secondTB b
ON a.ID = b.responceID
INNER JOIN thirdTB c
ON b.patientID = c.patientID
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins
You're probably looking for INNER JOIN or JOIN in general:
SELECT
response.id,
response.responce,
patient.patientid
FROM
`response_table` as `response`
INNER JOIN
`relation_table` as `relation`
ON
relation.responceid = response.id
INNER JOIN
`patient_table` as `patient`
ON
relation.patientid = patient.patientid
try
SELECT first.*
, third.*
FROM first
INNER JOIN second ON ( second.responseid = first.id )
INNER JOIN third ON ( third.patientid = second.patientid )
;
honestly, and no insult intended, if you have difficulties in coming up with queries like this one on your own, consider some training on db basics and db development, the sooner the better (just hoping i haven't blundered myself ... ;-)).

How to approach this SQL query

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)