Select multiple rows with MAX value - mysql

Given this table:
players(id,team,name,age)
I want to select the oldest player/s for each team
My query so far:
select p.team,p.name,MAX(p.age)
from players p
group by p.team
This query gives me the oldest player for each team but if there are multiple players with the same MAX age it selects only one of those at random.
How can I make it so it selects all of them?

This query:
select p.team, p.name, MAX(p.age)
from players p
group by p.team;
Should return a syntax error. The group by clause is inconsistent with the select, because p.name is not in the group by and not the argument to an aggregation function.
To do what you want, you can use a correlated subquery:
select p.*
from players p
where p.age = (select max(p2.age) from players p2 where p2.team = p.team);

Related

I want both distinct and nondistinct records in join mysql

SELECT distinct referrals.listing_id,submissions.listing_name
FROM submissions
INNER JOIN referrals ON referrals.listing_id=submissions.listing_id;
if i do
SELECT distinct referrals.listing_id,submissions.listing_name,referrals.timestamp
FROM submissions
INNER JOIN referrals ON referrals.listing_id=submissions.listing_id;
it gives me many others records without distinct listing_id
i want distinct listing_id with their timestamps(which are not distinct but are according to distinct listing_id)
Use aggregation, if you want one row per listing. For instance:
SELECT r.listing_id, s.listing_name, max(r.timestamp) as most_recent_timestamp
FROM submissions s INNER JOIN
referrals r
ON r.listing_id = s.listing_id
GROUP BY r.listing_id, s.listing_name;
SELECT DISTINCT applies to all the expressions in the SELECT.

Mysql: Count wins but only once per opponent

I'm looking for help using sum() in my SQL query:
Task: Count tournament wins of all players. (one number per player) (battles.result = 1 means Player1 wins)
SELECT members.id, members.name,
(
SELECT SUM(battles.result = 1)
FROM battles
WHERE members.id = battles.player1 AND battles.result=1 order by battles.gametime
( as wins,
FROM members
Next: Only count ONE result per two players.
So if there are multiple results of two players, count only the first result (first gametime).
I've already tried using order by battles.player2, but i guess there is a much better solution?
You can easily get the result by doing a join and aggregation instead. Try:
SELECT A.id, A.name, SUM(IFNULL(B.result,0)) wons
FROM members A LEFT JOIN battles B
ON A.id=B.player1
GROUP BY A.id, A.name;

Group_concat with columns of a particular type in the first

My tables are:
Parent_Child (Parent_SSN, Child_SSN)
Person (SSN, Name, age, sex)
School (Child_SSN, School_Name)
I want to select the parents(female,male) who have atleast one of their children in a particular school 'X'.I have a working query and my mysql query is:
select group_concat(p.name) from person p,parentchild pc,school s
where s.schoolname='X' and s.childssn=pc.childssn and p.ssn=pc.parentssn
group by pc.childssn
This displays the result as parent(male,female) but I want the result in (female,male) form and if I group it by parent.sex it displays results in individual rows and not in a single row.I am out of ideas.
Sample desired output:
name
Angela,Jim
Output of my above existing query:
name
Jim,Angela
Here's a Sql Fiddle for you.
SELECT DISTINCT group_concat(p.name ORDER BY p.sex Asc)
FROM Person p JOIN Parent_Child pc ON p.ssn=pc.Parent_SSN
JOIN School s ON s.Child_SSN = pc.Child_SSN
WHERE s.School_Name='X'
GROUP By pc.Child_SSN;
Try this, might possible using sort
SELECT GROUP_CONCAT( p.name ORDER BY p.name DESC ) from person p,parentchild pc,school s
where s.schoolname='X' and s.childssn=pc.childssn and p.ssn=pc.parentssn
group by pc.childssn
You can use ORDER BY inside the GROUP_CONCAT function in this way :
Try this
SELECT group_concat(DISTINCT p.name ORDER BY p.name Asc)
FROM person p JOIN parentchild pc ON p.ssn=pc.parentssn
JOIN school s ON s.childssn = pc.childssn
WHERE s.schoolname='X'
GROUP By pc.childssn
See http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function%5Fgroup-concat
I somehow managed to get the results using join here is my working query.
select * from (select distinct p.name from person p,parentchild pc,school s where s.schoolname='X' and s.childssn=pc.childssn and p.ssn=pc.parentssn and p.sex='F')q1 join (select distinct p.name from person p,parentchild pc,school s where s.schoolname='X' and s.childssn=pc.childssn and p.ssn=pc.parentssn and p.sex='M')q
I want to still know whether it is possible to sort items by a particular condition in group_concat result set which will affect it's postion in the group_concat result set.

MySQL is not using INDEX in subquery

I have these tables and queries as defined in sqlfiddle.
First my problem was to group people showing LEFT JOINed visits rows with the newest year. That I solved using subquery.
Now my problem is that that subquery is not using INDEX defined on visits table. That is causing my query to run nearly indefinitely on tables with approx 15000 rows each.
Here's the query. The goal is to list every person once with his newest (by year) record in visits table.
Unfortunately on large tables it gets real sloooow because it's not using INDEX in subquery.
SELECT *
FROM people
LEFT JOIN (
SELECT *
FROM visits
ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id
Does anyone know how to force MySQL to use INDEX already defined on visits table?
Your query:
SELECT *
FROM people
LEFT JOIN (
SELECT *
FROM visits
ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id;
First, is using non-standard SQL syntax (items appear in the SELECT list that are not part of the GROUP BY clause, are not aggregate functions and do not sepend on the grouping items). This can give indeterminate (semi-random) results.
Second, ( to avoid the indeterminate results) you have added an ORDER BY inside a subquery which (non-standard or not) is not documented anywhere in MySQL documentation that it should work as expected. So, it may be working now but it may not work in the not so distant future, when you upgrade to MySQL version X (where the optimizer will be clever enough to understand that ORDER BY inside a derived table is redundant and can be eliminated).
Try using this query:
SELECT
p.*, v.*
FROM
people AS p
LEFT JOIN
( SELECT
id_people
, MAX(year) AS year
FROM
visits
GROUP BY
id_people
) AS vm
JOIN
visits AS v
ON v.id_people = vm.id_people
AND v.year = vm.year
ON v.id_people = p.id;
The: SQL-fiddle
A compound index on (id_people, year) would help efficiency.
A different approach. It works fine if you limit the persons to a sensible limit (say 30) first and then join to the visits table:
SELECT
p.*, v.*
FROM
( SELECT *
FROM people
ORDER BY name
LIMIT 30
) AS p
LEFT JOIN
visits AS v
ON v.id_people = p.id
AND v.year =
( SELECT
year
FROM
visits
WHERE
id_people = p.id
ORDER BY
year DESC
LIMIT 1
)
ORDER BY name ;
Why do you have a subquery when all you need is a table name for joining?
It is also not obvious to me why your query has a GROUP BY clause in it. GROUP BY is ordinarily used with aggregate functions like MAX or COUNT, but you don't have those.
How about this? It may solve your problem.
SELECT people.id, people.name, MAX(visits.year) year
FROM people
JOIN visits ON people.id = visits.id_people
GROUP BY people.id, people.name
If you need to show the person, the most recent visit, and the note from the most recent visit, you're going to have to explicitly join the visits table again to the summary query (virtual table) like so.
SELECT a.id, a.name, a.year, v.note
FROM (
SELECT people.id, people.name, MAX(visits.year) year
FROM people
JOIN visits ON people.id = visits.id_people
GROUP BY people.id, people.name
)a
JOIN visits v ON (a.id = v.id_people and a.year = v.year)
Go fiddle: http://www.sqlfiddle.com/#!2/d67fc/20/0
If you need to show something for people that have never had a visit, you should try switching the JOIN items in my statement with LEFT JOIN.
As someone else wrote, an ORDER BY clause in a subquery is not standard, and generates unpredictable results. In your case it baffled the optimizer.
Edit: GROUP BY is a big hammer. Don't use it unless you need it. And, don't use it unless you use an aggregate function in the query.
Notice that if you have more than one row in visits for a person and the most recent year, this query will generate multiple rows for that person, one for each visit in that year. If you want just one row per person, and you DON'T need the note for the visit, then the first query will do the trick. If you have more than one visit for a person in a year, and you only need the latest one, you have to identify which row IS the latest one. Usually it will be the one with the highest ID number, but only you know that for sure. I added another person to your fiddle with that situation. http://www.sqlfiddle.com/#!2/4f644/2/0
This is complicated. But: if your visits.id numbers are automatically assigned and they are always in time order, you can simply report the highest visit id, and be guaranteed that you'll have the latest year. This will be a very efficient query.
SELECT p.id, p.name, v.year, v.note
FROM (
SELECT id_people, max(id) id
FROM visits
GROUP BY id_people
)m
JOIN people p ON (p.id = m.id_people)
JOIN visits v ON (m.id = v.id)
http://www.sqlfiddle.com/#!2/4f644/1/0 But this is not the way your example is set up. So you need another way to disambiguate your latest visit, so you just get one row per person. The only trick we have at our disposal is to use the largest id number.
So, we need to get a list of the visit.id numbers that are the latest ones, by this definition, from your tables. This query does that, with a MAX(year)...GROUP BY(id_people) nested inside a MAX(id)...GROUP BY(id_people) query.
SELECT v.id_people,
MAX(v.id) id
FROM (
SELECT id_people,
MAX(year) year
FROM visits
GROUP BY id_people
)p
JOIN visits v ON (p.id_people = v.id_people AND p.year = v.year)
GROUP BY v.id_people
The overall query (http://www.sqlfiddle.com/#!2/c2da2/1/0) is this.
SELECT p.id, p.name, v.year, v.note
FROM (
SELECT v.id_people,
MAX(v.id) id
FROM (
SELECT id_people,
MAX(year) year
FROM visits
GROUP BY id_people
)p
JOIN visits v ON ( p.id_people = v.id_people
AND p.year = v.year)
GROUP BY v.id_people
)m
JOIN people p ON (m.id_people = p.id)
JOIN visits v ON (m.id = v.id)
Disambiguation in SQL is a tricky business to learn, because it takes some time to wrap your head around the idea that there's no inherent order to rows in a DBMS.

Counting rows in a Query containing Join

I have a query that goes like this:
SELECT Product.local_price*Rate.exchange_rate AS 'US_price' FROM Product
INNER JOIN Rate ON Rate.currency = Product.currency
WHERE Product.type='TV'
HAVING US_price BETWEEN 500 AND 600;
How do I do a count on the number of TV sets that satisfy this query?
Table structure
Product Table: ID, type, local_price
Rate Table: currency, exchange_rate
Replace the HAVING US_price with AND Product.local_price * Rate.exchange_rate and just do a COUNT(Product.ID) in the SELECT clause:
SELECT COUNT(Product.ID)
FROM Product
INNER JOIN Rate ON Rate.currency = Product.currency
WHERE Product.type='TV'
AND Product.local_price * Rate.exchange_rate BETWEEN 500 AND 600;
You would want to use a HAVING if you wanted criteria on aggregated data, like this:
SELECT p.type, AVG(p.local_price)
FROM Product p
GROUP BY p.type
HAVING AVG(p.local_price) > 50
There's no need to use a HAVING clause here; its special semantics are only relevant when you have a GROUP BY clause. So, we can simply replace US_price in the HAVING clause with the expression that generates it, and move it into the WHERE clause; and then, use SELECT COUNT(*):
SELECT COUNT(*)
FROM Product
JOIN Rate
ON Rate.currency = Product.currency
WHERE Product.type = 'TV'
AND Product.US_price * Rate.exchange_rate BETWEEN 500 AND 600
;
Also, as a general rule — not needed in this case — you can always (or almost always?) wrap your entire query in SELECT COUNT(*) FROM (...) t to get the total number of rows it returns.