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

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

Related

MySQL query - Select statement from two tables with group by returning records with largest ids

I really need help from you, I've spend a lot of time already on trying to figure it out but without success :(
I have two tables:
What I need is to group everything by sea_id / bat_season and gain the greatest Id's for these seasons. So bat_id's 3 & 5 should be returned with their linked data.
But if there is no data in Table 2 I still should see details of two seasons without Table 2 details.
My closest result is here with the below statement:
SELECT b.bat_id, b.bat_trophies, b.bat_ranking, s.sea_id, s.sea_name, s.sea_start
FROM gvg_seasons s
LEFT JOIN (SELECT bat_id, bat_trophies, bat_ranking, bat_season FROM gvg_battles ORDER BY bat_id DESC LIMIT 1) b
ON s.sea_id = b.bat_season
WHERE s.sea_gl_id = 1
GROUP BY s.sea_id DESC
The result:
Result
If someone can help me here please I will be very grateful.
I haven't tried this as I didn't fancy transcribing the table data from your images but it should provide the result you are looking for.
The innermost sub-query gets the max(bat_id) per bat_season. This is joined back to the gvg_battles to give the latest battle per season.
SELECT *
FROM gvg_seasons s
LEFT JOIN (
SELECT b1.*
FROM gvg_battles b1
JOIN (
SELECT bat_season, MAX(bat_id) AS max_bat_id
FROM gvg_battles
GROUP BY bat_season
) b_max ON b1.bat_id = b_max.max_bat_id
) b2 ON s.sea_id = b2.bat_season;

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

Remove duplicates from LEFT JOIN query

I am using the following JOIN statement:
SELECT *
FROM students2014
JOIN notes2014 ON (students2014.Student = notes2014.NoteStudent)
WHERE students2014.Consultant='$Consultant'
ORDER BY students2014.LastName
to retrieve a list of students (students2014) and corresponding notes for each student stored in (notes2014).
Each student has multiple notes within the notes2014 table and each note has an ID that corresponds with each student's unique ID. The above statement is returning a the list of students but duplicating every student that has more than one note. I only want to display the latest note for each student (which is determined by the highest note ID).
Is this possible?
You need another join based on the MAX noteId you got from your select.
Something like this should do it (not tested; next time I'd recommed you to paste a link to http://sqlfiddle.com/ with your table structure and some sample data.
SELECT *
FROM students s
LEFT JOIN (
SELECT MAX(NoteId) max_id, NoteStudent
FROM notes
GROUP BY NoteStudent
) aux ON aux.NoteStudent = s.Student
LEFT JOIN notes n2 ON aux.max_id = n2.NoteId
If I may say so, the fact that a table is called students2014 is a big code smell. You'd be much better off with a students table and a year field, for many reasons (just a couple: you won't need to change your DB structure every year, querying across years is much, much easier, etc, etc). Perhaps you "inherited" this, but I thought I'd mention it.
GROUP the query by studentId and select the MAX of the noteId
Try :
SELECT
students2014.Student,
IFNULL(MAX(NoteId),0)
FROM students2014
LEFT JOIN notes2014 ON (students2014.Student = notes2014.NoteStudent)
WHERE students2014.Consultant='$Consultant'
GROUP BY students2014.Student
ORDER BY students2014.LastName

Getting object if count is less then a number

I have 2 simple tables - Firm and Groups. I also have a table FirmGroupsLink for making connections between them (connection is one to many).
Table Firm has attributes - FirmID, FirmName, City
Table Groups has attributes - GroupID, GroupName
Table FirmGroupsLink has attributes - FrmID, GrpID
Now I want to make a query, which will return all those firms, that have less groups then #num, so I write
SELECT FirmID, FirmName, City
FROM (Firm INNER JOIN FirmGroupsLink ON Firm.FirmID =
FirmGroupsLink.FrmID)
HAVING COUNT(FrmID)<#num
But it doesn't run, I try this in Microsoft Access, but it eventually should work for Sybase. Please show me, what I'm doing wrong.
Thank you in advance.
In order to count properly, you need to provide by which group you are couting.
The having clause, and moreover the count can't work if you are not grouping.
Here you are counting by Firm. In fact, because you need to retrieve information about the Firm, you are grouping by FirmId, FirmName and City, so the query should look like this:
SELECT Firm.FirmID, Firm.FirmName, Firm.City
FROM Firm
LEFT OUTER JOIN FirmGroupsLink
ON Firm.FirmID = FirmGroupsLink.FrmID
GROUP BY Firm.FirmID, Firm.FirmName, Firm.City
HAVING COUNT(FrmID) < #num
Note that I replace the INNER JOIN by a LEFT OUTER JOIN, because you might want Firm which doesn't belongs to any groups too.

Mysql join with sorting

I have two tables say: user and library. The library contains books sorted in a
certain way; a user can elect to sort his books in a certain way as well. The
structure (and sample data) for the two tables will look thus:
library
bookid position
10 1
12 2
14 3
16 4
user
userid bookid position
12669 12 1
12669 10 2
I want a query to return all the books for user, 12669, sorted by position i.e:
select bookid from user where userid = 12669 group by position
After it has return these sorted books, it should return the other bookids (not present in user) that are in the library. No bookid should be repeated. The result of these scenario will look thus:
12
10
14
16
In other words: All the books in library should be returned by this query but
with the books selected by user sorted according to user.position
I reckon I may need some kind of join statement for this. I tried:
select bookid from user u right join library l on u.bookid = l.bookid where u.userid = 12669 group by u.position
However, I get a syntax error for this. What is the best way to solve this 'problem'? Many thanks.
First of all, your posted query includes in its projection a column name which doesn't belong to either of the tables involved.
Secondly, you are using GROUP BY where sorting is carried out by ORDER BY.
Third point: As #Romil points out, the reference to the USER table in the WHERE clause overrides the outer join, and effectively enforces an inner join. So you need to select from an inline view on the USER table.
Finally, to get the sort order you want, you need to band all the USER.POSITIONs first. This version of your query uses IFNULL to assign an extremely high value to any rows which don't have a joined USER record. So it will sort by all the returned USER.POSITION values then by LIBRARY.POSITION for all the remaining books.
select l.bookid
from ( select * from user
where userid = 12669 ) u
right join library l
on (u.bookid = l.bookid)
order by ifnull(u.position, 999999999) , l.position
NB: if you are indexing the Library of Babel and so might have enough shelves to support more than 999999999 positions just bump up that subsituted value.
"This didn't work for me. It didn't return the bookids in the
arrangement I wanted. "
Frankly I find that surprising. Here is a SqlFiddle which definitely returns your sample data in the order you specify. So I repeat my earlier question: do your posted table descriptions match your actual tables exactly ?
This should do the trick :)
SELECT l.bookid
FROM library l
LEFT OUTER JOIN user u on u.bookid = l.bookid
WHERE u.userid = 12669
ORDER BY isnull(u.position, 99999), l.position
SELECT library.bookid bookid
FROM library
LEFT JOIN (SELECT *
FROM [user]
WHERE [USER].userid = 12669) users
ON [USERs].bookid = library.bookid
ORDER BY [USERs].userid DESC,
[USERs].position,
library.position
Thanks APC, Romil and aF. I finally figured out a solution that didn't use a join.
This worked for me:
(
SELECT bookid
FROM user
WHERE userid = 12669
ORDER BY position
)
UNION (
SELECT bookid
FROM library
)
The first query selects the ordered bookids from user then the union statement selects every other book from library and adds this to the result set. The arrangement of this later group wasn't important.