Counting average from two queries - mysql

I'm trying get the average from two select queries. In the first select i'm counting all "votes" per district, now i want to devide it by total sum of votes in voivodeship(It can have more than one district)
select void.name, d.name, t.name, COUNT(v.idvotes) as "Percent of votes"
from voivodeship void, politicialteam t, votes v, candidates c, person p, district d
where c.idcandidates = v.idcandidates
and p.idperson = c.idperson
and c.idpoliticialteam = t.idpoliticialteam
and void.idvoivodeship = d.idvoivodeship
and c.iddistrict = d.iddistrict
GROUP by d.name ORDER by void.name ASC
SELECT Count(vo.idvotes)
from votes vo, candidates c, district d, voivodeship v
where d.idvoivodeship = v.idvoivodeship
and d.iddistrict = c.iddistrict
and vo.idcandidates = c.idcandidates
GROUP by v.name
Total for "dolnośląskie": 119987
In my example i want merge this two queries to get result 40038/119987, 40078/119987 etc..

You can achieve this result in two different way, depending on your DBMS.
If your DBMS supports Extended SQL queries, you should try to get a partition on void.name and aggregate the result in order to compute your percentage:
SELECT void.name,
d.name,
t.name,
COUNT(v.idvotes) as "Percent of votes",
COUNT(v.idvotes)/SUM(COUNT(v.idvoted))*100 OVER (PARTITION BY void.name) as "Votes percentage"
FROM voivodeship void, politicialteam t, votes v, candidates c, person p, district d
where c.idcandidates = v.idcandidates and
p.idperson = c.idperson and
c.idpoliticialteam = t.idpoliticialteam and
void.idvoivodeship = d.idvoivodeship and
c.iddistrict = d.iddistrict
GROUP BY d.name, void.name
ORDER BY void.name ASC
If your DBMS does not support Extended SQL, you can achieve the same result nesting the two queries:
SELECT void.name,
d.name,
t.name,
COUNT(v.idvotes) as "Percent of votes",
COUNT(v.idvotes)/(
SELECT Count(vo.idvotes)
FROM votes vo1, candidates c1, district d1, voivodeship v1
WHERE d1.idvoivodeship = v1.idvoivodeship and
d1.iddistrict = c1.iddistrict and
vo1.idcandidates = c1.idcandidates and
d1.name = d.name
GROUP BY v.name
)*100 AS "Percentage of votes"
FROM voivodeship void, politicialteam t, votes v, candidates c, person p, district d
where c.idcandidates = v.idcandidates and
p.idperson = c.idperson and
c.idpoliticialteam = t.idpoliticialteam and
void.idvoivodeship = d.idvoivodeship and
c.iddistrict = d.iddistrict
GROUP BY d.name, void.name
ORDER BY void.name ASC
N.B. In the last solution you can merge the queries adding another JOIN condition on the nested query such as
d1.name = d.name

Related

Group by not getting the expected results in mysql

I have the next query:
SELECT DISTINCT
bt.name, b.id
FROM
ports po,
cities c,
provinces p,
countries co,
states s,
translations t,
element_types et,
languages l,
boat_models bm,
boat_types bt,
boats b
JOIN
boat_prices bprf ON b.id = bprf.boat_id
AND bprf.checkin_date IS NULL
AND bprf.duration_id IS NULL
WHERE
t.element_translation = 'España'
AND et.name = 'Country'
AND s.name = 'confirmed'
AND s.id = b.state_id
AND l.locale = 'es'
AND t.language_id = l.id
AND t.element_type_id = et.id
AND t.element_id = p.country_id
AND c.province_id = p.id
AND po.city_id = c.id
AND b.port_id = po.id
AND bm.id = b.boat_model_id
AND bt.id = bm.boat_type_id
That is working perfectly and returning 9 rows:
'BOAT_TYPE_CATAMARAN','13707'
'BOAT_TYPE_SAILBOAT','13700'
'BOAT_TYPE_SAILBOAT','13701'
'BOAT_TYPE_SAILBOAT','13702'
'BOAT_TYPE_SAILBOAT','13703'
'BOAT_TYPE_SAILBOAT','13704'
'BOAT_TYPE_SAILBOAT','13705'
'BOAT_TYPE_SAILBOAT','13706'
'BOAT_TYPE_SAILBOAT','13708'
I want to group the results by boat type and get the number of boats per type.
However, when I do:
SELECT DISTINCT
bt.name, COUNT(b.id) AS num_boats
FROM
ports po,
cities c,
provinces p,
countries co,
states s,
translations t,
element_types et,
languages l,
boat_models bm,
boat_types bt,
boats b
JOIN
boat_prices bprf ON b.id = bprf.boat_id
AND bprf.checkin_date IS NULL
AND bprf.duration_id IS NULL
WHERE
t.element_translation = 'España'
AND et.name = 'Country'
AND s.name = 'confirmed'
AND s.id = b.state_id
AND l.locale = 'es'
AND t.language_id = l.id
AND t.element_type_id = et.id
AND t.element_id = p.country_id
AND c.province_id = p.id
AND po.city_id = c.id
AND b.port_id = po.id
AND bm.id = b.boat_model_id
AND bt.id = bm.boat_type_id
GROUP BY bt.name
ORDER BY bt.name
I´m getting:
'BOAT_TYPE_CATAMARAN','241'
'BOAT_TYPE_SAILBOAT','1928'
but according to the first query, I´m expecting
'BOAT_TYPE_CATAMARAN','1'
'BOAT_TYPE_SAILBOAT','8'
What am I missing?
I suspect that you want:
SELECT bt.name, COUNT(DISTINCT b.id) AS num_boats
FROM ...
WHERE ...
GROUP BY bt.name
ORDER BY bt.name
That is: move the DISTINCT within the COUNT() rather than directly in the SELECT.
Generally speaking, DISTINCT and GROUP BY do not go along well together; DISTINCT is already aggregation in essence, so mixing both is usually not relevant.
Note that your syntax uses old-school, implicit joins (with a comma in the FROM clause): you should be using standard joins (with the ON keyword), whose syntax has been state-of-the-art for decades.
You are doing a distinct in your first query so you are 'hiding' a lot if rows that gets doubled because of your join.

Issue returning row content data alongside Max(score) by class & round, using group by

I want to return the personal best for a user, by class & round; but the Date Shot is coming out incorrect. Help please - so frustrating!
SELECT
c.Class,
r.Round,
h.shootdate as 'Date Shot',
max(h.Score) AS 'Personal Best'
FROM history h, classes c, rounds r
WHERE c.id = h.classid AND r.id = h.roundid AND h.userid = 1
GROUP BY c.Class, r.Round
You could use a self join on history table to pick a row with maximum score for each user per classid and roundid
SELECT
c.Class,
r.Round,
h.shootdate as 'Date Shot',
h.Score AS 'Personal Best'
FROM history h
JOIN (
SELECT classid, roundid, max(score) score
FROM history
WHERE userid = 1
GROUP BY classid, roundid
) h1 ON h.classid = h1.classid AND h.roundid = h1.roundid AND h.score = h1.score
JOIN classes c ON c.id = h.classid
JOIN rounds r ON r.id = h.roundid
-- WHERE h.userid = 1 // not necessary
In your query you are picking shootdate which is not present in group by that is why you are not getting correct value where score is max, Also use explicit join syntax to relate your tables

MySQL subquery unknown column for complex query

I've got the following sqlfiddle http://sqlfiddle.com/#!2/324628/1
I need to create a query that returns the id and the position (ranking) of each student within his class; the position is sorted in descending order according to the value of their academic average stored inside the academic_averages table.
(e.g. the first from class 1, second from the class 1, and so on... the first from class 2, the second from class 2...)
Here's the query:
SELECT students.id,
(SELECT x.position
FROM (
SELECT t.student_id, t.value, #rownum := #rownum + 1 AS position
FROM (
SELECT aa.student_id, aa.value
FROM academic_averages AS aa
INNER JOIN students AS s ON s.id = aa.student_id
INNER JOIN classes_students AS cs ON cs.student_id = s.id
INNER JOIN classes_academic_years AS cas ON cas.id = cs.class_academic_year_id
INNER JOIN classes_academic_years as cas2 on cas2.class_id = cas.class_id
INNER JOIN classes_students as cs2 on cs2.class_academic_year_id = cas2.id
INNER JOIN students as s2 on s2.id = cs2.student_id
WHERE s2.id = 243
AND cas.academic_year_id = 4
AND aa.academic_year_id = 4
GROUP BY aa.student_id
ORDER BY abs(aa.value) DESC
) t
JOIN (SELECT #rownum := 0) r
) AS x WHERE x.student_id = students.id ) AS ranking_by_class
FROM students
However, since it contains a subquery, I cannot change the WHERE from the inner most query to s2.id = students.id because it throws an error (unknown column).
I've tried using an INNER JOIN instead of subqueries, but no luck so far.
Does anyone have a solution?
Thanks
LE: Performance wise the query must be optimized
LE: Here's the structure of the tables:
academic_averages:
id
student_id
value
academic_year_id
classes_academic_years:
id
class_id
name
grade
academic_year_id
classes_students:
id
class_academic_year_id
student_id
classes:
id
school_id
students:
id
The desired output should be student_id, position.
There seems to be some issues with sql fiddle, meanwhile here's the schema: http://snippi.com/s/db8za8k
SELECT x.id
, x.position
, x.academic_average
FROM (SELECT
s.id
, #rownum := #rownum + 1 position
, av.value academic_average
FROM students s
JOIN classes_students cs ON s.id = cs.student_id
JOIN classes_academic_years cay ON cay.id = cs.class_academic_year_id
JOIN academic_averages av ON av.student_id = s.id
WHERE cay.academic_year_id = 4 -- change these two parameters in
AND av.academic_year_id = 4 -- the subquery for different years
ORDER BY av.value DESC) x,
(SELECT #rownum := 0) y
ORDER BY academic_average DESC
I think the above query should work for you.
I've made the assumption that the academic ranking position is determined in a descending order by academic average.
I don't have access to your dataset, so I've added three extra lines, two to select the students' academic average and one to order the result in descending order according to the academic average. This should help you verify that it works as intended. If you run the query, and it works, it should display records with position starting at 1 and incrementing by 1.
In production I would omit these fragments in order to get the result set you specify:
1. , x.academic_average
2. , av.value academic_average
3. ORDER BY academic_average DESC
Edit following elaboration in comments by OP (students' ranking required by class)
This query should give you students' positions by class. If you want to get rid of some fields, you can wrap the SELECT in another SELECT, or ignore the columns once the dataset is extracted to another language.
SELECT
x.student_id
, x.cay_class_id
, x.academic_average
, if(#classid = x.cay_class_id, #rownum := #rownum + 1, #rownum := 1) position
, #classid := x.cay_class_id
FROM (SELECT
s.id student_id
, cay.class_id cay_class_id
, av.value academic_average
FROM students s
JOIN classes_students cs ON s.id = cs.student_id
JOIN classes_academic_years cay ON cay.id = cs.class_academic_year_id
JOIN academic_averages av ON av.student_id = s.id
WHERE cay.academic_year_id = 4 -- change these two parameters in
AND av.academic_year_id = 4 -- the subquery for different years
ORDER BY cay.class_id, av.value DESC) x,
(SELECT #classid := 0, #rownum := 0) y

Trying to add one last SUM() column to my query in SQL Server 2008

I have the first query which is producing correct results. What I need is I need to add the sum of values as a last column grouped by surveyid. I can't insert Sum(c.value) into the first query because it is an aggregate function. I have the correct query as my second query below. I know there's pivot functionality but not sure if it can be used here. I do realize that there will be repetition but that's okay.
'first query
SELECT
A.PATIENTID, B.STUDENTNUMBER, c.surveyid,
convert(varchar, A.CreatedDate, 107),
C.QuestionID, C.Value, D.Question
FROM
dbo.Survey A, dbo.Patient B, [dbo].[SurveyQuestionAnswer] C, [dbo].[LookupQuestions] D
WHERE
A.PATIENTID = B.ID
and c.SurveyID = A.ID
and c.QuestionID = d.ID
and c.questionid <> 10
ORDER BY
A.PATIENTID
'second query
select
c.surveyid,SUM(c.value) as scores
from
dbo.SurveyQuestionAnswer c
group by
c.SurveyID
order by
SurveyID '---not important
You can use SUM if you add the OVER clause. In this case:
SELECT
A.PATIENTID, B.STUDENTNUMBER, c.surveyid,
convert(varchar, A.CreatedDate, 107),
C.QuestionID, C.Value, D.Question,
SUM(c.Value) OVER(PARTITION BY c.surveyid) scores
FROM
dbo.Survey A
INNER JOIN dbo.Patient B
ON A.PATIENTID = B.ID
INNER JOIN [dbo].[SurveyQuestionAnswer] C
ON c.SurveyID = A.ID
INNER JOIN [dbo].[LookupQuestions] D
ON c.QuestionID = d.ID
WHERE
c.questionid <> 10
ORDER BY
A.PATIENTID
You could use something like this:
SELECT
s.PATIENTID, p.STUDENTNUMBER, sqa.surveyid,
CONVERT(varchar, s.CreatedDate, 107),
sqa.QuestionID, sqa.Value, lq.Question,
Scores = (SELECT SUM(Value) FROM dbo.SurveyQuestionAnswer s2 WHERE s2.SurveyID = s.ID)
FROM
dbo.Survey s
INNER JOIN
dbo.Patient p ON s.PatientID = p.ID
INNER JOIN
[dbo].[SurveyQuestionAnswer] sqa ON sqa.SurveyID = s.ID
INNER JOIN
[dbo].[LookupQuestions] lq ON sqa.QuestionID = lq.ID
WHERE
sqa.questionid <> 10
ORDER BY
s.PATIENTID
By having a subquery with the SUM(...) you should be able to get that sum as a single value and you don't need to use any grouping function

MySQL MAX associated with a second column

I am trying to get the highest and lowest value associated with an account for a 1 year timeframe for a country. This data is pulled from one table.
I will have the highest account return for account one and lowest account return for account two for a country. So 1 result per country.
I've got the following but it doesn't work properly, it actually provides me the highest and lowest values from an incorrect account as it should only work with accounts that have 1 year timeframe as well.
Also forgot to add perhaps ordering the overall result by dpromo_one only do these countries eg country in ('united states','united kingdom','south africa','india','australia') for these selected countries only. Its just got quite complex that it went way over my head.
SELECT DISTINCT acc2.account_name AS account_one, acc5.account_name AS account_two,
MAX( acc2.dpromo_rate ) AS dpromo_one, MIN( acc5.dpromo_rate ) AS dpromo_two,
acc2.deposit_term, acc2.country
FROM accounts acc2
INNER JOIN accounts acc5 ON acc2.country = acc5.country
WHERE acc2.type =2
AND acc5.type =2
AND acc2.deposit_term = '1 Year'
GROUP BY country
overall output example could be the following
for line 1:
Country Bank Highest Bank Lowest
USA BOFA 1yr 1% Wells Fargo 1yr 0.5%
UK HSBC 1yr 0.5% Halifax 1yr 0.25%
Australia CBA 1yr 0.4% NAB 1yr 0.1%
eg the accounts table has the following fields for example that are relevant
account_name
country
dpromo_rate
deposit_term
note that we are having both accounts and rates side by side. my code does this but incorrectly though and thats why it also explains why i have aliases for duplicate field names.
To give you the basics in your output example:-
SELECT DISTINCT z.county, b.account_name, b.dpromo_rate, d.account_name, d.dpromo_rate
FROM accounts z
INNER JOIN (SELECT country, type, MAX(dpromo_rate) AS MaxRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) a
ON z.country = a.country AND z.type = a.type
INNER JOIN accounts b
ON a.country = b.country and a.MaxRate = b.dpromo_rate AND a.type = b.type
INNER JOIN (SELECT country, type, MIN(dpromo_rate) AS MinRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) c
ON z.country = c.country AND z.type = c.type
INNER JOIN accounts d
ON c.country = d.country and c.MinRate = d.dpromo_rate AND c.type = d.type
This is just getting the country, account name with the max rate, the actual max rate, account name with the min rate and the actual min rate.
Not sure where deposit term and provider are wanted in the output, but they would be easy to get from the b or d alias tables.
Note that that this will make a mess should you have multiple accounts in a country which all share the max or min rates.
To limit it to a few countries and order by the max rate:-
SELECT DISTINCT z.county, b.account_name, b.dpromo_rate, d.account_name, d.dpromo_rate
FROM accounts z
INNER JOIN (SELECT country, type, MAX(dpromo_rate) AS MaxRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) a
ON z.country = a.country AND z.type = a.type
INNER JOIN accounts b
ON a.country = b.country and a.MaxRate = b.dpromo_rate AND a.type = b.type
INNER JOIN (SELECT country, type, MIN(dpromo_rate) AS MinRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) c
ON z.country = c.country AND z.type = c.type
INNER JOIN accounts d
ON c.country = d.country and c.MinRate = d.dpromo_rate AND c.type = d.type
WHERE z.country IN ('united states', 'united kingdom', 'south africa', 'india', 'australia')
ORDER BY b.dpromo_rate
To limit it to one per country then you can do this:-
SELECT z.county, b.account_name, b.dpromo_rate, d.account_name, d.dpromo_rate
FROM accounts z
INNER JOIN (SELECT country, type, MAX(dpromo_rate) AS MaxRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) a
ON z.country = a.country AND z.type = a.type
INNER JOIN accounts b
ON a.country = b.country and a.MaxRate = b.dpromo_rate AND a.type = b.type
INNER JOIN (SELECT country, type, MIN(dpromo_rate) AS MinRate FROM accounts WHERE type = 2 AND deposit_term = '1 Year' GROUP BY country, type) c
ON z.country = c.country AND z.type = c.type
INNER JOIN accounts d
ON c.country = d.country and c.MinRate = d.dpromo_rate AND c.type = d.type
WHERE z.country IN ('united states', 'united kingdom', 'south africa', 'india', 'australia')
GROUP BY z.county
ORDER BY b.dpromo_rate
Note that if 2 accounts in a country have the same rate which is the highest rate for that country then only one will be returned. Which one that is returned is not certain.
It is doing exactly what MySQL says it will do. Columns not included in the group by clause have arbitrary values. In most other databases, the query would simply fail with a syntax error.
Here is a trick to get the names of the accounts:
SELECT substring_index(group_concat(acc2.account_name order by acc2.dpromo_rate desc), ',', 1) AS account_one,
substring_index(group_concat(acc5.account_name order by acc5.dpromo_rate asc), ',', 1) AS account_two,
MAX( acc2.dpromo_rate ) AS dpromo_one, MIN( acc5.dpromo_rate ) AS dpromo_two,
acc2.deposit_term, acc2.country
FROM accounts acc2
INNER JOIN accounts acc5 ON acc2.country = acc5.country
WHERE acc2.type =2 AND acc5.type =2 AND acc2.deposit_term = '1 Year'
GROUP BY country
I think you can simplify the query to a simple aggregation. I don't see why you are doing a join:
SELECT substring_index(group_concat(acc.account_name order by acc.dpromo_rate desc), ',', 1) AS account_one,
substring_index(group_concat(acc.account_name order by acc.dpromo_rate asc), ',', 1) AS account_two,
MAX(acc.dpromo_rate) AS dpromo_one, MIN(acc.dpromo_rate) AS dpromo_two,
acc.deposit_term, acc.country
FROM accounts acc
WHERE acc.type = 2 and acc.deposit_term = '1 Year'
GROUP BY country;
If you intend for the deposit term to only apply to the max, then replace the max with:
max(case when acc.deposit_term = '1 Year' then acc.dpromo_rate end) as dpromo_one
I thing this will give result
select max(dpromo_rate), min(dpromo_rate), country, account_name, provider from accounts
where deposit_term = '1 Year' group by country, provider, account_name