SQL Left Join on more then 5 tables - mysql

The code is retreating what I want it to retrieve, though it does 4 times more that I need.
I have a table called property with a PK property_ID. A property has a title, short description, long description and it belongs to a point of interest, which in turn is connected to a city. Title, short&long description have all independent link tables, in which a link is created between a property and the corresponding translation in the translation table.
I am using left joins to collect all the desired information, below in the image attachment you can see the results.
The desired result would be that it would return only 2 rows, with the corresponding translation of the title, short and long description. At the moment is returning 8 rows.
The issue that I noticed is it orders the columns in a weird way.
If you look closer, you can notice that title and titleLangCode are ordered correctly after titleLangCode, while long and short descriptions are ordered by their own langCode. Grouping them wont work since they are ordered differently, so ordering them has no effect, union didnĀ“t work either because of the column numbers, even tried distinct but with no avail.
The code:
select
property.*,
title_translation.title,
title_translation.langCode as titleLangCode,
short_desc_translation.shortDescription,
long_desc_translation.longDescription,
short_desc_translation.langCode as shortLangCode,
long_desc_translation.langCode as longLangCode,
property_city_poi.city_poi_link_ID
from
property
left join
title_link
on
property.property_ID = title_link.property_ID
left join
title_translation
on
title_link.title_link_ID = title_translation.title_link_ID
left join
short_desc_link
on
property.property_ID = short_desc_link.property_ID
left join
short_desc_translation
on
short_desc_link.short_desc_link_ID = short_desc_translation.short_desc_link_ID
left join
long_desc_link
on
property.property_ID = long_desc_link.property_ID
left join
long_desc_translation
on
long_desc_link.long_desc_link_ID = long_desc_translation.long_desc_link_ID
left join
property_city_poi
on
property.property_ID = property_city_poi.property_ID
where
property.property_ID = 10
Is there a possibility of somehow combing limit with group ? I tried but have not succeed.
title_link
title_link_ID
property_ID
dateCreated
title_translation
title_translation_ID
title_link_ID
langCode
title
short_desc_link
short_desc_link_ID
property_ID
dateCreated
short_desc_translation
short_desc_translation_ID
short_desc_link_ID
langCode
shortDescription
long_desc_link
long_desc_link_ID
property_ID
dateCreated
long_desc_translation
long_desc_translation_ID
long_desc_link_ID
langCode
shortDescription

If I understand this correctly, the issue is that you have multiple translation tables which have a 1 to many relationship to the property. So after the first join you have 2 rows then 4 and then 8 with all combinations of languages.
You can limit this by joining on 2 conditions.
... property p
INNER JOIN titel_link tl on p.id = tl.propertyid
INNER JOIN short_desk_link sdl on p.id = tl.propertyid AND tl.langCode = sdl.langCode
I've simplified this a little and used aliases for table names to shorten the join conditions.
Edit: I'd say this is also a sign of bad database design. you should probably introduce a table 'language' and then a mapping table property-translation mapping. Not sure if this is under your control but a setup with tables like this would be better.
Property: all the details for the property.
Language: A listing of all the languages with Id.
FieldId: A list of named columns that you have in your database.
Translation: A combination of Property, Language and Field on which you can then have a single translation for that field.

Related

Select from 3 tables if result is empty

I'm using 3 tables to collect data from. The proces looks like:
User write VIN to form
Script search in table 1 for case_id and country base on that vin
number
After that he use case_id and country for search in table number 2
and get calculation id from there
Base on that calculation id and case id it search in 3th table
.
My script looks like this:
SELECT
cases.case_id,
cases.lastcalc_model_options,
cases.country,
calculations.calculation_id,
calculations.license,
positions.text
FROM cases
INNER JOIN calculations ON(cases.case_id =calculations.case_id
AND cases.country = calculations.country)
INNER JOIN positions ON(calculations.case_id = positions.case_id
AND calculations.calculation_id = positions.calculation_id)
WHERE vin ='ABCDEFGH'
This select work correctly, problem start when for example there is for example no result in table positions with that case_id and calculation_id. Instead of give back atleast everything it found in other tables it return NOTHING.
Is there a way to change this kind of komplex SELECT to return everything it found not return something only when every condition is TRUE?
Your problem is the INNER JOIN. Using INNER JOIN your result only contains entries present in all tables. Try using LEFT JOIN instead.
SELECT
cases.case_id,
cases.lastcalc_model_options,
cases.country,
calculations.calculation_id,
calculations.license,
positions.text
FROM cases
LEFT JOIN calculations ON(cases.case_id =calculations.case_id
AND cases.country = calculations.country)
LEFT JOIN positions ON(calculations.case_id = positions.case_id
AND calculations.calculation_id = positions.calculation_id)
WHERE vin ='ABCDEFGH'
See this stackoverlow answer for some more indepth information.
INNER JOIN returns rows from both tables only if there is a match between the columns in both tables.
You may try LEFT JOIN or FULL OUTER JOIN instead.

What order to join multiple (over 3) tables and where to put conditions?

I've done quite a bit of reading and testing, and I can get the information I want out of individual queries, but I can't seem to join them to get everything in one table.
Goal - List all containers with the related blocks and the name of the AdminRole.
Here's a visual of the relationships between the tables and fields I need:
Table Structure Picture
Since I can't attach pictures yet, here's the table descriptions:
AdminACL
adminID (=adminrole_admin.adminID) (EDIT: =adminrole.ID)
objectID (=Container.ID)
ObjectType (Condition "Container")
adminrole_admin (EDIT: table not needed)
adminroleID (=adminrole.ID)
adminID (=AdminACL.adminID)
adminrole
ID (=adminrole_admin.adminroleID) (EDIT: =AdminACL.adminid)
name <- Desired field in results table [bonus points for condition not like 'hidden_%']
Container
ID (=AdminACL.objectID)
name <- Desired field in results table
container_block
containerID (=Container.ID)
blockID (=Block.ID)
Block
ID (=container_block.blockID)
name <- Desired field in results table
I've got select statements for each piece, but since it makes for exceptionally messy queries, I tried joins, but end up with empty sets. I'm not sure which table to start with and if I need to use different types of joins and/or in different orders.
Here's the last query I tried before giving up:
SELECT C.name, B.name, A.name
FROM container C
JOIN (SELECT adminid,objected from adminacl where objecttype='Container') ACL ON ACL.objectid=C.id
JOIN adminrole_admin AA ON ACL.adminid=AA.adminid
JOIN (select id,name from adminrole where name not like 'hidden_%') A ON AA.adminid=A.id
JOIN container_block CB ON C.id=CB.containerid
JOIN block B ON CB.blockid=B.id;
As much as I'd love the answer, I'd also like to understand how to structure this type of query in the future. I'm pretty sure I'm missing something obvious - just having a hard time relating all the other examples I've looked at with this. TIA!
EDIT: It turns out that adminacl.adminid=adminrole.id, which made things a lot more simple. Also ended up needing the 'hidden' admin roles, which were specific user based permissions added on top of the roles. Based on Russ's answer, this is what my final query ended up being:
SELECT container.name, block.name, adminrole.name
FROM block
LEFT JOIN container_block ON container_block.blockid=block.id
LEFT JOIN container ON container.id=container_block.containerid
LEFT JOIN adminacl ON adminacl.objectid=container.id
JOIN adminrole ON adminrole.id=adminacl.adminid
WHERE
adminacl.objecttype='Container'
AND block.blockstatus !=1 #to exclude unassigned blocks
ORDER BY container.name, block.name;
I like to start from what I actually want the largest quantity of. From your question, I think you want a list of blocks with containers and admin roles on there, so I started there and left-joined on the additional information to create the list.
SELECT Block.name, Container.name, adminrole.name from
Block
LEFT JOIN container_block ON container_block.containerid = Block.id
LEFT JOIN Container ON Container.id = container_block.containerid
LEFT JOIN AdminACL ON AdminACL.objectid = Container.id
RIGHT JOIN adminrole_admin ON adminrole_admin.adminid #there could be multiple roles
LEFT JOIN adminrole ON adminrole.id = adminrole_admin.adminroleID
WHERE
AdminACL.ObjectType = "Container"
AND adminrole.name not like 'hidden_%';
# shouldn't you have a `hidden` column on the table, instead of prefixing the name?
Disclaimer: I wrote this from memory and didn't even test it on anything, but give it a try, it might set you on the right path.
LEFT JOIN is going to exclude rows that fail the join condition, so you shouldn't get lots of irrelevant blanks, but I'm assuming that there can be many adminroles, so I'm using a RIGHT JOIN to produce rows that contain all possible combinations of the adminrole and Block. This will mean that the same block appears with different adminroles and the same adminrole will appear on multiple blocks. Your application will need to handle this duplication appropriately.

MySQL LEFT JOIN with optional value in second table

I have two tables: projects and user_licenses.
I'd like to grab the entire list of projects from the database, as well as the user's current license state. The licenses table has a user ID field which I check against a $_SESSION variable for the value. The thing is, they might not have a license, or a non-logged in visitor may want to see the projects list. My question is this: How can I get the data from the left table always display, but only grab data for that row from the right table when certain conditions are met?
The query I have at the moment is this:
SELECT Projects.*,
UserLicenses.*
FROM Projects
LEFT JOIN UserLicenses ON Projects.id = UserLicenses.project_id
WHERE UserLicenses.user_id = 12
ORDER BY name ASC
Add any extra conditions to the on clause of the left join. They will only affect the joined table.
SELECT Projects.*,
UserLicenses.*
FROM Projects
LEFT JOIN UserLicenses
ON Projects.id = UserLicenses.project_id
and UserLicenses.user_id = 12
and UserLicences.Question = '6*7'
and UserLicences.Answer = 42
ORDER BY name ASC
This will return projects without matching licenses.
Move the UserLicenses condition away from WHERE, and up to the JOIN condition. By having it in the WHERE part, you will never see those "left" rows because they are filtered away.
You can also probably use WHERE (UserLicenses.user_id = 12 OR UserLicenses.user_id IS NULL)
Don't do that. Just move it to the join condition like this:
LEFT JOIN UserLicenses ON
(Projects.id = UserLicenses.project_id AND UserLicenses.user_id = 12)
You can use LEFT JOIN
If its conditions match then values show otherwise null value shows.

MySQL Multiple select

I have an indextable which hold a content_id and type colomn.
(eg. content_id=1, type=audio. content_id=1, type=video) Would it be possible to get the right data based upon content_id from the right tables (tables: contentvideo, contentaudio)?
I take it you want to retrieve the content via a single select statement? If the layout of your content tables is exactly the same you can use UNION:
SELECT ca.* FROM contentaudio ca, indextable it
WHERE ca.content_id = it.content_id
AND it.content_type = 'audio'
UNION
SELECT cv.* FROM contentvideo cv, indextable it
WHERE cv.content_id = it.content_id
AND it.content_type = 'video'
ORDER BY content_id /* or whatever */
Note that if content_id is not unique across all of your content tables, you may get multiple records (of different types) for the same content_id. Also note that if you're selecting a particular record based on some indextable key, you'll need to add it to each part of the union.
If your content tables have different layouts, you can use an OUTER JOIN instead and retrieve your various content types as part of one record (missing ones will be returned as NULLs):
SELECT * FROM indextable it
LEFT OUTER JOIN contentaudio ca ON it.content_id = ca.content_id
LEFT OUTER JOIN contentvideo cv ON it.content_id = cv.content_id
WHERE it.some_key = ?
Note that content_type is not involved in this case at all (nor do you really need it for UNION select either).
Have you tried CASE, or IF?
http://dev.mysql.com/doc/refman/6.0/en/case-statement.html

MySQL -- joining then joining then joining again

MySQL setup: step by step.
programs -> linked to --> speakers (by program_id)
At this point, it's easy for me to query all the data:
SELECT *
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
Nice and easy.
The trick for me is this. My speakers table is also linked to a third table, "books." So in the "speakers" table, I have "book_id" and in the "books" table, the book_id is linked to a name.
I've tried this (including a WHERE you'll notice):
SELECT *
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
JOIN books on speakers.book_id = books.book_id
WHERE programs.category_id = 1
LIMIT 5
No results.
My questions:
What am I doing wrong?
What's the most efficient way to make this query?
Basically, I want to get back all the programs data and the books data, but instead of the book_id, I need it to come back as the book name (from the 3rd table).
Thanks in advance for your help.
UPDATE:
(rather than opening a brand new question)
The left join worked for me. However, I have a new problem. Multiple books can be assigned to a single speaker.
Using the left join, returns two rows!! What do I need to add to return only a single row, but separate the two books.
is there any chance that the books table doesn't have any matching columns for speakers.book_id?
Try using a left join which will still return the program/speaker combinations, even if there are no matches in books.
SELECT *
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
LEFT JOIN books on speakers.book_id = books.book_id
WHERE programs.category_id = 1
LIMIT 5
Btw, could you post the table schemas for all tables involved, and exactly what output (or reasonable representation) you'd expect to get?
Edit: Response to op author comment
you can use group by and group_concat to put all the books on one row.
e.g.
SELECT speakers.speaker_id,
speakers.speaker_name,
programs.program_id,
programs.program_name,
group_concat(books.book_name)
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
LEFT JOIN books on speakers.book_id = books.book_id
WHERE programs.category_id = 1
GROUP BY speakers.id
LIMIT 5
Note: since I don't know the exact column names, these may be off
That's typically efficient. There is some kind of assumption you are making that isn't true. Do your speakers have books assigned? If they don't that last JOIN should be a LEFT JOIN.
This kind of query is typically pretty efficient, since you almost certainly have primary keys as indexes. The main issue would be whether your indexes are covering (which is more likely to occur if you don't use SELECT *, but instead select only the columns you need).