SQL Join with multiple conditions for one column - mysql

In my situation, I've two tables, a table with all institutions profile and a table which represents material studied by each of these institutions.
In a search, I want to look for institution's name which studied specifics type of material. So it's possible to look for institution which studied "Wood" and "Metal", just "Metal" etc...
I've tried the following query :
SELECT p.name
FROM q1_institution_profiles p
INNER JOIN q9_materials_studied_by_institution pf
ON pf.id_institution = p.id_institution
WHERE pf.id_material_studied = 10 AND pf.id_material_studied = 8
However result is empty because there is a problem when I have multiple conditions (here id_material_studied must be equal 8 and 10).
Is there a way to achieve this properly or do I have to do on join for each criteria ?
Thank you in advance for answer

I think this is what you want:
SELECT p.name
FROM q1_institution_profiles p INNER JOIN
q9_materials_studied_by_institution pf
ON pf.id_institution = p.id_institution
WHERE pf.id_material_studied IN (8, 10)
GROUP BY p.name
HAVING COUNT(DISTINCT pf.id_material_studied) = 2;
That is, get names where there are rows with each of the two values.
The WHERE clause selects only materials 8 and 10. The GROUP BY then groups these by p.name. The question is: Does a single name have both these materials? Well, that is what count(distinct) does.

Related

SQL Left Join on more then 5 tables

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.

mysql query with dynamic return column name

I have 3 tables,Products, Languages and Products_translation.
How can I retrieve one Product and all translation at same time in one query ?
I have this
SELECT p.*, pt.description FROM Products p
LEFT JOIN Products_translation pt ON p.id=pt.product_id
LEFT JOIN Languages l ON pt.language =l.code
I have 3 languages, but it only retrieves one field name 'description' and I wanted it to return 3 (the number of languages), with something like description_en, description_es, description_fr
It's possible to make something like pt.description AS 'description'+'l.code' ?
This is a very common question and I'm fairly sure it's been answered many times (for example here: Mysql: joining tables for translation records). Anyway, if you have only 3 languages, just do this:
SELECT p.*, pt_en.description as description_en, pt_es.description as description_es, pt_fr.description as description_fr FROM Products p
LEFT JOIN Products_translation pt_en ON (p.id=pt.product_id and pt.language = 'en')
LEFT JOIN Products_translation pt_es ON (p.id=pt.product_id and pt.language = 'es')
LEFT JOIN Products_translation pt_fr ON (p.id=pt.product_id and pt.language = 'fr')
If you have more than 3, or a varying number, search pivoting or pivot table for more info. It's not easy to do in SQL so usually it's faster to just select your products, select all the translations of those products in a separate query, and construct the result you want outside of the database.

MySQL Query Unique Results From Join

Struggling to find the right answer for this so hopefully someone can help. It maybe so simple that I've overlooked something obvious and making it more difficult than I should be.
I have two tables - titles [including titleIDs and titleNames], and groups [including groupIDs with title IDs they are associated with]. A titleID can have many groupIDs attached.
I'm trying to write a query that brings me back results of TitleIDs that match a criteria of groupIDs that have been selected.
So I've tried
SELECT * FROM titles INNER JOIN groups ON titles.titleID = groups.titleID WHERE
groups.groupID = 6 AND
groups.groupID = 24 AND
groups.groupID = 53
So I want to return results of only titles that are only associated with ALL these group IDS.
The numbers will actually be replaced by what someone selects from a few tickboxes, but have hardcoded them in for purposes of this example.
I tried experimenting with a subquery but I couldn't get it to work, also I believe Subs can slow things down and I'm already going to be dealing with a lot of data.
The plan is for someone to select one or more groupIDs from a list and then return only results of Titles that are associated with all the GroupsIDs selected.
Any pointers, clues, advice on this would be really welcome.
Thanks
You can do so by using in() for the group ids and matching the count of distinct groups foreach title,if 3 group ids provided so count for each title groups must be 3 so the title that has exactly these 3 groups will be returned
SELECT * FROM titles t
INNER JOIN groups g ON t.titleID = g.titleID
WHERE g.groupID IN(6,24,53)
GROUP BY t.titleID
HAVING COUNT(DISTINCT g.groupID) = 3

Coming up with MySQL query on multiple tables

I need to come up with a query which I am finding to be a bit complex.
This is my database structure:
The user will add/enter filters, and depending on the filters, there will be displayed correspondent Collections of magazines and newspapers.
The possible filters are:
Magazine/Newspaper name
fromDate
toDate
Select if the results are only from Magazine or Newspaper or both
If the Collection has been analysed or not
Well, suposing that the user has entered a name, both dates, both magazine and newspaper and only collections that have been analysed, how would the query look like? I would like to display the collectionDate, newspaper/magazine name, from and to dates.
I have been trying to figure out the query but I am struggling a bit.
Does anyone have suggestions? Hope that everything is clear :)
Thanks!
SELECT m.name as mag_name,
n.name as news_paper_name,
c.fromDate,
c.toDate
FROM Collections AS C
INNER JOIN Target AS T ON
C.TargetID = T.TargetID
INNER JOIN Magazine AS M ON
T.TargetID = M.id
INNER JOIN Newspaper AS N ON
T.TargerID = N.id
INNER JOIN Analysis AS A ON
C.CollectionID = A.CollectionID
This should work from a joins perspective. I am assuming that when something is anaylzed that there is a record under Analysis and not one when it has been anaylzed. Just add a where clause and whatever information you want to filter by.

Using Joins, Group By and Sub Queries, Oh My!

I have a database with a table for details of ponies, another for details of contacts (owners and breeders), and then several other small tables for parameters (colours, counties, area codes, etc.). To give me a list of existing pony profiles, with their various details given, i use the following query:
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
ORDER BY profiles.ProfileYearOfBirth ASC $limit
In the above sample, the 'profiles' table is my primary table (holding the Ponies info), 'contacts' is second in importance holding as it does the owner and breeder info. The lesser parameter tables can be identified by their prm_ prefix. The above query works fine, but i want to do more.
The first big issue is that I wish to GROUP the results by gender: Stallions, Mares, Geldings... I used << GROUP BY prm_breedgender.BreedGender >> or << GROUP BY ProfileBreedGenderID >> before my ORDER BY line, but than only returns two results from all my available profiles. I have read up on this, and apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause. How to do this however, gets me verrrrrrry confused. Step by step help here would be fantabulous.
As a further note on the above - You may have noticed the $limit var at the end of my query. This is for pagination, a feature I want to keep. I shouldn't think that's an issue however.
My secondary issue is more of an organisational one. You can see where I have pulled my Owner information from the contacts table here:
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
I could add another stipulation:
AND profiles.ProfileBreederID = contacts.ContactID
with the intention of being able to list a pony's Owner and Breeder, where info on either is available. I'm not sure how to echo out this info though, as $row['ContactName'] could apply in either the capacity of owner OR breeder.
Is this a case of simply running two queries rather than one? Assigning a variable $foo to the first run of the query, then just run another separate query altogether and assign $bar to those results? Or is there a smarter way of doing it all in the one query (e.g. $row['ContactName']First-iteration, $row['ContactName']Second-iteration)? Advice here would be much appreciated.
And That's it! I've tried to be as clear as possible, and do really appreciate any help or advice at all you can give. Thanks in advance.
##########################################################################EDIT
My query currently stands as an amalgam of that provided by Cularis and Symcbean:
SELECT *
FROM (
profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
)
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
It works insofar as the results are being arranged as I had hoped: i.e. by age and gender. However, I cannot seem to get the alias' to work in relation to the contacts queries (breeder and owner). No error is displayed, and neither are any Owners or Breeders. Any further clarification on this would be hugely appreciated.
P.s. I dropped the alias given to the final LEFT JOIN by Symcbean's example, as I could not get the resulting ORDER BY statement to work for me - my own fault, I'm certain. Nonetheless, it works now although this may be what is causing the issue with the contacts query.
GROUP in SQL terms means using aggregate functions over a group of entries. I guess what you want is order by gender:
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
This will output all Stallions, etc. next to each other.
To also get the breeders contact, you need to join with the contacts table again, using an alias:
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
To further expand on what #cularis stated, group by is for aggregations down to the lowest level of "grouping" criteria. For example, and I'm not doing per your specific tables, but you'll see the impact. Say you want to show a page grouped by Breed. Then, a user picks a breed and they can see all entries of that breed.
PonyID ProfileGenderID Breeder
1 1 1
2 1 1
3 2 2
4 3 3
5 1 2
6 1 3
7 2 3
Assuming your Gender table is a lookup where ex:
BreedGenderID Description
1 Stallion
2 Mare
3 Geldings
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
select
BG.Description,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
group by
BG.Description
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description CountPerBreed
Geldings 1
Mare 2
Stallion 4
change the "order by" clause to "order by CountsPerBreed Desc" (for descending) and you would get
Description CountPerBreed
Stallion 4
Mare 2
Geldings 1
To expand, if you wanted the aggregations to be broken down per breeder... It is a best practice to group by all things that are NOT AGGREGATES (such as MIN(), MAX(), AVG(), COUNT(), SUM(), etc)
select
BG.Description,
BR.BreaderName,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
join Breeders BR
on p.Breeder = BR.BreaderID
group by
BG.Description,
BR.BreaderName
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description BreaderName CountPerBreed
Geldings Bill 1
Mare John 1
Mare Sally 1
Stallion George 2
Stallion Tom 1
Stallion Wayne 1
As you can see, the more granularity you provide to the group by, the aggregation per that level is smaller.
Your join conditions otherwise are obviously understood from what you've provided. Hopefully this sample clearly provides what the querying process will do. Your group by does not have to be the same as the final order... its just common to see so someone looking at the results is not trying to guess how the data was organized.
In your sample, you had an order by the birth year. When doing an aggregation, you will never have the specific birth year of a single pony to so order by... UNLESS.... You included the YEAR( ProfileYearOfBirth ) as BirthYear as a column, and included that WITH your group by... Such as having 100 ponies 1 yr old and 37 at 2 yrs old of a given breed.
It would have been helpful if you'd provided details of the table structure and approximate numbers of rows. Also using '*' for a SELECT is a messy practice - and will cause you problems later (see below).
What version of MySQL is this?
apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause
Not necessarily since v4 (? IIRC), you could just wrap your query in a consolidating select (but move the limit into the outer select:
SELECT ProfileGenderID, COUNT(*)
FROM (
[your query without the LIMIT]
) ilv
GROUP BY ProfileGenderID
LIMIT $limit;
(note you can't ORDER BY ilv.ProfileYearOfBirth since it is not a selected column / group by expression)
How many records/columns do you have in prm_breedgender? Is it just Stallions, Mares, Geldings...? Do you think this list is likely to change? Do you have ponies with multiple genders? I suspect that this domain would be better represented by an enum in the profiles table.
with the intention of being able to list a pony's Owner and Breeder,
Using the code you suggest, you'll only get returned instances where the owner and breeder are the same! You need to add a second instance of the contacts table with a different alias to get them all, e.g.
SELECT *
FROM (
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts ownerContact
ON profiles.ProfileOwnerID = ownerContact.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
) ilv LEFT JOIN contacts breederContact
ON ilv.ProfileBreederID = breederContact.ContactID
ORDER BY ilv.ProfileYearOfBirth ASC $limit