Subquery in SQL - mysql

So I have an Olympic database, the basic layout is that there's a competitors table with competitornum, givenname, and familyname (other columns aren't necessary for this)
There's also a results table with competitornum, and place (between 1 and 8).
I'm trying to get the givenname and familyname and total number of gold, silver, and bronze medals (place = 1, 2 or 3)
Here's what I've got so far:
SELECT c.Givenname, c.Familyname, places AS TotalPlaces
FROM Competitors c,
(SELECT COUNT(*) as places
FROM Results r, Competitors c
WHERE r.Competitornum = c.Competitornum
AND r.Place > 0
AND r.Place < 4) q
However it returns everyone's name, and the same total places (which happens to be 78). e.g.
John Smith 78
Cassandra Jane 78
Bob Turner 78

Currently your query is conducting CROSS JOIN producing caertesian product.
When using aggregate function (count, max, min,...), the records should be group by non-aggregated column. Try this,
SELECT c.Givenname, c.Familyname, COUNT(r.places) AS TotalPlaces
FROM Competitors c INNER JOIN Results r
ON r.Competitornum = c.Competitornum
WHERE r.place IN (1,2,3)
GROUP BY c.Givenname, c.Familyname

Related

SQL GROUP BY "HAVING" the required rows

Is there a succinct way of using HAVING to check if the required rows are within the GROUP BY?
With the example date:
turtle_id name
1 Mike
2 Ralph
3 Leo
4 Don
and
turtle_id crush_for
1 Pizza
1 April Oneil
2 April Oneil
3 Pizza
3 April Oneil
4 Pizza
4 Pizza
4 Science
4 April Oneil
And the SQL:
SELECT turtle.name
FROM turtle
JOIN turtle_crush ON turtle_crush.turtle_id = turtle.turtle_id
WHERE turtle_crush.crush_for IN ('Pizza', 'April Oneil')
GROUP BY turtle.turtle_id
HAVING (a crush on both "Pizza" and "April Oneil")
I realize I could do something like HAVING COUNT(*) > 1, but that would give a false positive for Don (id 4) because he likes 'Pizza' twice.
Edit:
Just adding a WHERE clause will return Ralph where he doesn't have a crush_for 'Pizza'
This should work:
SELECT t.turtle_name
FROM turtle t
INNER JOIN (SELECT turtle_id
FROM turtle_crush
WHERE crush_for IN ('Pizza','April Oneil')
GROUP BY turtle_id
HAVING COUNT(DISTINCT crush_for) = 2) tc
on t.turtle_id = tc.turtle_id;
In this code, the subquery first filter the results where crush_for is either 'Pizza' or 'April Oneil'. Then it groups by turtle_id, with another condition of choosing those turtle_ids that have 2 different crush_for values (hence ensuring that you get only the turtle_ids that have both crushes). Then it's joined with the turtle table to get the name.
Put the list of crushes in the WHERE clause, group by turtle ID, count the distinct values of crush types then keep only the groups that have at least 2 values (or how many crushes you put in the query):
SELECT turtle.name
FROM turtle
INNER JOIN turtle_crush ON turtle_crush.turtle_id = turtle.turtle_id
WHERE crush_for IN ("Pizza", "April Oneil")
GROUP BY turtle.turtle_id
HAVING COUNT(DISTINCT crush_for) = 2

SQL Incorrect SUMS from multiple JOINS

I'm trying to sum multiple tables using Joins and Sums in MySQL and not having much success.
My Tables (Unnecessary Columns Removed)
Students
idStudent studentname studentyear
1 foobar 11
2 barfoo 11
3 thing 8
Athletics_Results
idResult idStudent points
1 1 14
2 1 11
3 3 7
4 2 9
Team_Results
idTeamResults year points
1 11 9
2 8 8
3 7 14
So let me explain about the tables, because I admit they're poorly named and designed.
Students holds the basic info about each student, including their year and name. Each student has a unique ID.
Athletics_Results stores the results from athletics events. The idStudent column is a foreign key and relates to idStudent in the student column. So student foobar (idStudent 1) has scored 14 and 11 points in the example.
Team_Results stores results from events that more than one student took part in. It just stores the year group and points.
The Aim
I want to be able to produce a sum of points for each year - combined from both athletics_results and team_results. EG:
year points
7 14 <-- No results in a_r, just 14 points in t_r
8 15 <-- 7 points in a_r (idResult 4) and 8 in t_r
11 43 <-- 14, 11, 9 points in a_r and 9 in t_r
What I've tried
For testing purposes, I've not tried combining the a_r scores and t_r scores yet but left them as two columns so I can see what's going on.
The first query I tried:
SELECT students.studentyear as syear, SUM(athletics_results.points) as score, SUM(team_results.points) as team_score
FROM students
JOIN team_results ON students.studentyear = team_results.year
JOIN athletics_results ON students.idStudent = athletics_results.idStudent
GROUP BY syear;
This gave different rows for each year (as desired) but had incorrect SUMS. I learnt this was due to not grouping the joins.
I then created this code:
SELECT studentyear as sYear, teamPoints, AthleticsPoints
FROM students st
JOIN (SELECT year, SUM(tm.points) as teamPoints
FROM team_results tm
GROUP BY year) tr ON st.studentyear = tr.year
JOIN (SELECT idStudent, SUM(atr.points) as AthleticsPoints
FROM athletics_results atr
) ar ON st.idStudent = ar.idStudent
Which gave correct SUMS but only returned one year group row (e.g the scores for Year 11).
EDIT - SQLFiddle here: http://sqlfiddle.com/#!9/dbc16/. This is with my actual test data which is a bigger sample than the data I posted here.
http://sqlfiddle.com/#!9/ad111/7
SELECT tr.`year`, COALESCE(tr.points,0)+COALESCE(SUM(ar.points),0)
FROM Team_Results tr
LEFT JOIN Students s
ON tr.`year`=s.studentyear
LEFT JOIN Athletics_Results ar
ON s.idStudent = ar.idStudent
GROUP BY tr.year
According to your comment and fiddle provided
check http://sqlfiddle.com/#!9/dbc16/3
SELECT tr.`year`, COALESCE(tr.points,0)+COALESCE(SUM(ar.points),0)
FROM (
SELECT `year`, SUM(points) as points
FROM Team_Results
GROUP BY `year`) tr
LEFT JOIN Students s
ON tr.`year`=s.studentyear
LEFT JOIN Athletics_Results ar
ON s.idStudent = ar.idStudent
GROUP BY tr.year
Try this http://sqlfiddle.com/#!9/2bfb1/1/0
SELECT
year, SUM(points)
FROM
((SELECT
a.year, SUM(b.points) AS points
FROM
student a
JOIN at_result b ON b.student_id = a.id
GROUP BY a.year) UNION (SELECT
a.year, SUM(a.points) AS points
FROM
t_result a
GROUP BY a.year)) c
GROUP BY year;
On your data I get:
year points
7 14
8 15
11 43
Can be done in multiple ways. My first thought is:
SELECT idStudent, year, SUM(points) AS totalPoints FROM (
SELECT a.idStudent, c.year, a.points+b.points AS points
FROM students a
INNER JOIN Athletics_Results b ON a.idStudent=b.idStudent
INNER JOIN Team_Results c ON a.studentyear=c.year) d
GROUP BY idStudent,year

NOT DISTINCT query in mySQL

I have been asked to create a query for this on a simple employee database columns include:
ninumber - first name - last name - address - SuperVisorNiNumber
The employees and supervisors are all held in the same table and are referenced by their ninumbers. The query I've been asked to build is:
v. Find the NI Numbers, first and last names of employees and NI numbers of supervisors where the employees share that supervisor and both employees and supervisors work in department 8. You will refer to the employee relation twice was done in query vi of Practical 2. Your results should be presented in columns with the following titles ‘Employee NI number’, ‘First Name’, ‘Last Name’ and ‘Supervisor NI Number’.
Therefore I created this query:
SELECT e1.ninumber,
e1.fname,
e1.minit,
e1.lname,
e1.address,
e1.superNiNumber,
COUNT(*) AS nrOfOccurences
FROM employee AS e1,
employee AS e2
WHERE e1.dno = 8
AND e1.superNiNumber = e2.ninumber
AND e2.dno = 8
GROUP BY e1.superNiNumber
HAVING COUNT(*) > 1
I couldn't do a not distinct query to work out this part of the question - "where the employees share that supervisor". This query returns a grouping of the rows which in turn hides some of the rows I want to show.
My question is: Is my query correct for the question and can I do a NON DISTINCT query in mySQL to get the database to return all of the fields instead of grouping them together.
Reutrn results from my query
NInumber fname minit lname address supervisorNiNum number of occerences
666666601 Jill J Jarvis 6234 Lincoln, Antrim, UK 666666600 2
666666607 Gerald D Small 122 Ball Street, Letterkenny, IRL 666666602 3
666666604 Billie J King 556 WAshington, Antrim, UK 666666603 2
Thanks.
In your result table column description, I see no minit, address and number of occurrences. Therefore I would simplify your select to:
SELECT e1.ninumber,
e1.fname,
e1.lname,
e1.superNiNumber,
FROM employee AS e1,
employee AS e2
WHERE e1.dno = 8
AND e1.superNiNumber = e2.ninumber
AND e2.dno = 8
and (select count(*) from employee e3
where e3.superNiNumber = e1.superNiNumber) > 1;
The accepted answer is quite slow in performance. After a bit of searching I managed to produce a faster running equivalent:
SELECT e1.ninumber,
e1.fname,
e1.lname,
e1.superNiNumber
FROM employee AS e1, (SELECT superNiNumber,
COUNT(*) AS count
FROM employee
GROUP BY superNiNumber
HAVING count > 1) AS e2
WHERE e1.superNiNumber = e2.superNiNumber
Credit: http://www.programmingforums.org/thread14669.html

Grouping and Accumulating Records at the same time

I am writing a query against an advanced many-to-many table in my database. I call it an advanced table because it is a many-to-many table with and extra field. The table maps data between the fields table and the students table. The fields table holds potential fields that a student can used, kind of like a contact system (i.e. name, school, address, etc). The studentvalues table that I need to query against holds the field id, student id, and the field answer (i.e. studentid=1; fieldid=2; response=Dave Long).
So my table looks like this:
What I need to do is take a few passed in values and create a grouped accumulated report. I would like to do as much in the SQL as possible.
So that data that I have will be the group by field (a field id), the cumulative field (a field id) and I need to group the students by the group by field and then in each group count the amount of students in the cumulative fields.
So for example I have this data
ID STUDENTID FIELDID RESPONSE
1 1 2 *(city)* Wallingford
2 1 3 *(state)* CT
3 2 2 *(city)* Wallingford
4 2 3 *(state)* CT
5 3 2 *(city)* Berlin
6 3 3 *(state)* CT
7 4 2 *(city)* Costa Mesa
8 4 3 *(state)* CA
I am hoping to write one query that I can generate a report that looks like this:
CA - 1 Student
Costa Mesa 1
CT - 3 Students
Berlin 1
Wallingford 2
Is this possible to do with a single SQL statement or do I have to get all the groups and then loop over them?
EDIT Here is the code that I have gotten so far, but it doesn't give the proper stateSubtotal (the stateSubtotal is the same as the citySubtotal)
SELECT state, count(state) AS stateSubtotal, city, count(city) AS citySubtotal
FROM(
SELECT s1.response AS city, s2.response AS state
FROM studentvalues s1
INNER JOIN studentvalues s2
ON s1.studentid = s2.studentid
WHERE s1.fieldid = 5
AND s2.fieldid = 6
) t
GROUP BY city, state
So to make a table that looks like that, I would assume something like
State StateSubtotal City CitySubtotal
CA 1 Costa Mesa 1
CT 3 Berlin 1
CT 3 Wallingford 2
Would be what you want. We can't just group on Response, since if you had a student answer LA for city, and another student that responds LA for state (Louisiana) they would add. Also, if the same city is in different states, we need to first lay out the association between a city and a state by joining on the student id.
edit - indeed, flawed first approach. The different aggregates need different groupings, so really, one select per aggregation is required. This gives the right result but it's ugly and I bet it could be improved on. If you were on SQL Server I would think a CTE would help but that's not an option.
select t2.stateAbb, stateSubtotal, t2.city, t2.citySubtotal from
(
select city, count(city) as citySubTotal, stateAbb from (
select s1.Response as city, s2.Response as StateAbb
from aaa s1 inner join aaa s2 on s1.studentId = s2.studentId
where s1.fieldId = 2 and s2.fieldId=3
) t1
group by city, stateabb
) t2 inner join (
select stateAbb, count(stateabb) as stateSubTotal from (
select s1.Response as city, s2.Response as StateAbb
from aaa s1 inner join aaa s2 on s1.studentId = s2.studentId
where s1.fieldId = 2 and s2.fieldId=3
) t3
group by stateabb
) t4 on t2.stateabb = t4.stateabb

two queries give different results in the same database

Please help. I'm using MySQL 5.1.30 Community Edition.
I have four tables: nts, operator, country, cooperationtype
table `nts` has one column(`operatorId`) which is a foreign key to column `id` in table `operator` and one column(`voice`) which is a foreign key to column `id` in table cooperationtype
table operator has one column(`country_id`) which is a foreign key to column (`id`) in table country
I want to get the counts of operators and countries where all of the value of voice not equals to 'N/A' and grouped them by cooperationtype.id with this query:
SELECT cooperationtype.id AS cooptype,
COUNT(DISTINCT country_id) AS country, COUNT(DISTINCT operatorId) AS operator
FROM nts INNER JOIN operator ON operator.id = nts.operatorId INNER JOIN country ON operator.country_id = country.id
INNER JOIN cooperationtype ON cooperationtype.id = nts.voice
WHERE cooperationtype.code <> 'N/A' GROUP BY cooperationtype.id
I got this result:
cooptype country operator
1 128 348
2 11 11
3 15 17
The sum of this query is 154 countries and 376 operators.
But then when I want to get all of the counts of operators and countries where all of the value of voice not equals to 'N/A', regardless the of cooperationtype.id with this query:
SELECT COUNT(DISTINCT country_id) AS country, COUNT(DISTINCT operatorId) AS operator
FROM nts INNER JOIN operator ON operator.id = nts.operatorId INNER JOIN country ON operator.country_id = country.id
INNER JOIN cooperationtype ON cooperationtype.id = nts.voice
WHERE cooperationtype.code <> 'N/A'
I got this result:
country operator
133 372
My questions are:
Why is the sum of the result from the first query doesn't equal to the result from the second query?
Which one is the right result?
Data example:
voice country operator
1 US 1
1 US 2
1 UK 3
1 UK 4
2 US 1
2 US 2
For the first query, the data should generate:
cooptype country operator
1 2 4
2 2 2
For the second query, the data should generate:
country operator
2 4
Why is the sum of the result from the first query doesn't equal to the result from the second query?
Because you use COUNT(DISTINCT).
It counts distinct records group-wise.
Your first query counts two records with the same country but different cooptype twice (since it groups by cooptype), while the second one counts them once.
Which one is the right result?
Both are right.
For the given data:
cooptype country
1 US
1 US
1 UK
1 UK
2 US
2 US
the first query will return:
1 2
2 1
and the second will return
2
, since you have:
2 distinct countries in cooptype = 1 (US and UK)
1 distinct country in cooptype = 2 (US)
2 distinct countries overall (US and UK)
Which is "right" in your definition of "right", depends, well, on this definition.
If you just want the second query to match the results of the first one, use
SELECT COUNT(DISTINCT cootype, country_id) AS country,
COUNT(DISTINCT cooptype, operatorId) AS operator
FROM nts
INNER JOIN
operator
ON operator.id = nts.operatorId
INNER JOIN
country
ON operator.country_id = country.id
INNER JOIN
cooperationtype
ON cooperationtype.id = nts.voice
WHERE cooperationtype.code <> 'N/A'
but, again, this may be as wrong as your first query is.
For these data:
cooptype country operator
1 US 1
1 US 1
1 UK 2
1 UK 2
2 US 1
2 US 1
, what would be a correct resultset?