Select row with max value from each group in Oracle SQL [duplicate] - sql

This question already has answers here:
Fetch the rows which have the Max value for a column for each distinct value of another column
(35 answers)
Closed 1 year ago.
I have table people containing people, their city and their money balance:
id city_id money
1 1 25
2 1 13
3 2 97
4 2 102
5 2 37
Now, I would like to select richest person from each city. How can I do that using Oracle SQL? Desired result is:
id city_id money
1 1 25
4 2 102
Something like that would be useful:
SELECT * as tmp FROM people GROUP BY city_id HAVING money = MAX(money)

You should be thinking "filtering", not "aggregation", because you want the entire row. You can use a subquery:
select p.*
from people p
where p.money = (select max(p2.money) from people p2 where p2.city_id = p.city_id);

You can use DENSE_RANK() analytic function through grouping by city_id(by using partition by clause) and descendingly ordering by money within the subquery to pick the returned values equal to 1 within the main query in order to determine the richest person including ties(the people having the same amount of money in each city) such as
SELECT id, city_id, money
FROM( SELECT p.*,
DENSE_RANK() OVER ( PARTITION BY city_id ORDER BY money DESC ) AS dr
FROM people p )
WHERE dr = 1

You can use RANK() as its flexible as you can get richest or top N richest
SELECT
id, city_id, money
FROM (
SELECT
p.* ,RANK() OVER (PARTITION BY city_id ORDER BY money DESC ) as rank_per_city
FROM
people p )
WHERE
rank_per_city = 1

Related

Return row with the highest count (like city with most transactions, when there are multiple cities)

I have a table with the following rows:
customer_id
city
transaction_id
date
I want to pull the city which has the highest number of transactions (count(transaction_id)) over a certain time period. A customer can have transactions in multiple cities.
What's the right syntax to achieve this?
UPDATE:
customer A can have 5 transactions in NYC and 10 transactions in Boston. I just want the customer record of the 10 transactions in Boston to be returned. How do I do this and what would have to cities that have the same number of transactions per customer_id?
if you need just top city name count then you can use below approach
select city,count(transaction_id) as cnt
From YourTable
where date=condition --- that you need
group by city
order by cnt desc
limit 1
if you need customerwise highest city name then use row_number()
with cte as
(
select customerid,city,count(*) as cnt
from table_name group by customerid,city
), cte1 as
(
select * ,row_number()over(partition by cusomerid order by cnt desc) rn
from cte
) select * from cte1 where rn=1

How to count a temporary variable in SQLite?

I am working on a personal analytics project and I need to filter a SQL table. My SQL knowledge is very basic and moreover, I know that in Oracle but in this case I have to use SQLite and it seems to be quite different.
For example, suppose the table is
student physics chemistry maths history english
Brian 78 62 100 40 50
Bill 80 70 95 50 60
Brian 80 40 90 95 60
The table has repetition.
I asked a question earlier today, using the same example above, which would let me rank the subjects for each student.
How to RANK multiple columns of an entire table?
What I want to do now is find out which students had Maths in the top 3 among all subjects and group the table for each student. So the goal is to find out how many times did Brian have Maths in Top 3 of his scores.
IT WeiHan's answer to the previous question (https://www.db-fiddle.com/f/bjui5W1VWmHXcqKAhK5iBD/0 ) worked perfectly and displayed the rank of the subjects for each row. I used their answer and tried to modify it for this purpose.
with cte as (
select student,'physics' as class,physics as score from Table1 union all
select student,'chemistry' as class,chemistry as score from Table1 union all
select student,'maths' as class,maths as score from Table1 union all
select student,'history' as class,history as score from Table1 union all
select student,'english' as class,english as score from Table1
)
SELECT name,class,score,rnk,
(CASE
WHEN class = "maths" AND rnk <=3 THEN 1
ELSE 0
END) as maths_rank
FROM
(select student,class,score,RANK() OVER (partition by student order by score desc) rnk
from cte)
which gives a table like
name class score rnk maths_rank
Brian maths 100 1 1
I want to be able to count the maths_rank values or sum it (as it contains 1 or 0 values) and group the table on student name. I tried to count the maths_rank variable but that didn't work and resulted in errors. Please help me out with a solution.
If I understand correctly, you are on the right path. I think you just need a where clause:
with cte as (
select student,'physics' as class,physics as score from Table1 union all
select student,'chemistry' as class,chemistry as score from Table1 union all
select student,'maths' as class,maths as score from Table1 union all
select student,'history' as class,history as score from Table1 union all
select student,'english' as class,english as score from Table1
)
select t.*
from (select t.*,
rank() over (partition by student order by score desc) as subject_rank
from cte t
) t
where class = 'maths' and subject_rank <= 3;
Edit:
If you want the number of times maths was in the top 3, then:
select student, sum(case when class = 'maths' and subject_rank <= 3 then 1 else 0 end) as maths_top3
from (select t.*,
rank() over (partition by student order by score desc) as subject_rank
from cte t
) t
group by student;

Select by greatest sum, but without the sum in the result

I need to select the top score of all combined attempts by a player and I need to use a WITH clause.
create table scorecard(
id integer primary key,
player_name varchar(20));
create table scores(
id integer references scorecard,
attempt integer,
score numeric
primary key(id, attempt));
Sample Data for scorecard:
id player_name
1 Bob
2 Steve
3 Joe
4 Rob
Sample data for scores:
id attempt score
1 1 50
1 2 45
2 1 10
2 2 20
3 1 40
3 2 35
4 1 0
4 2 95
The results would simply look like this:
player_name
Bob
Rob
But would only be Bob if Rob had scored less than 95 total. I've gotten so far as to have the name and the total scores that they got in two columns using this:
select scorecard.player_name, sum(scores.score)
from scorecard
left join scores
on scorecard.id= scores.id
group by scorecard.name
order by sum(scores.score) desc;
But how do I just get the names of the highest score (or scores if tied).
And remember, it should be using a WITH clause.
Who ever told you to "use a WITH clause" was missing a more efficient solution. To just get the (possibly multiple) winners:
SELECT c.player_name
FROM scorecard c
JOIN (
SELECT id, rank() OVER (ORDER BY sum(score) DESC) AS rnk
FROM scores
GROUP BY 1
) s USING (id)
WHERE s.rnk = 1;
A plain subquery is typically faster than a CTE. If you must use a WITH clause:
WITH top_score AS (
SELECT id, rank() OVER (ORDER BY sum(score) DESC) AS rnk
FROM scores
GROUP BY 1
)
SELECT c.player_name
FROM scorecard c
JOIN top_score s USING (id)
WHERE s.rnk = 1;
SQL Fiddle.
You could add a final ORDER BY c.player_name to get a stable sort order, but that's not requested.
The key feature of the query is that you can run a window function like rank() over the result of an aggregate function. Related:
Postgres window function and group by exception
Get the distinct sum of a joined table column
Can try something like follows.
With (SELECT id, sum(score) as sum_scores
FROM scores
group by id) as sumScoresTable,
With (SELECT max(score) as max_scores
FROM scores
group by id) as maxScoresTable
select player_name
FROM scorecard
WHERE scorecard.id in (SELECT sumScoresTable.id
from sumScoresTable
where sumScoresTable.score = (select maxScoresTable.score from maxScoresTable)
Try this code:
WITH CTE AS (
SELECT ID, RANK() OVER(ORDER BY SumScore DESC) As R
FROM (
SELECT ID, SUM(score) AS SumScore
FROM scores
GROUP BY ID )
)
SELECT player_name
FROM scorecard
WHERE ID IN (SELECT ID FROM CTE WHERE R = 1)

Get all rows with one of the top 2 values in a column

I have a table with multiple entries and I have ordered it according to a sales criterion. So, if the entries are like:
Item Sales
a 10
b 10
c 9
d 8
e 8
f 7
I want to extract the items with the highest and second highest number of sales. As such,
I would want to extract a, b and c.
Is there any function in PostgreSQL that can help with this?
To include all rows with one of the top two sales values, you could use the dense_rank() window function:
WITH x AS (
SELECT *
,dense_rank() OVER (ORDER BY sales DESC) AS rnk
FROM tbl
)
SELECT item, sales
FROM x
WHERE rnk < 3;
You need PostgreSQL 8.4 or later for that.
For older versions, you could:
SELECT *
FROM tbl
JOIN (
SELECT sales
FROM tbl
GROUP BY 1
ORDER BY 1 DESC
LIMIT 2
) t1 USING (sales)
Use ORDER BY and LIMIT:
SELECT Item, Sales
FROM mytable
ORDER BY Sales DESC
LIMIT 2;
Results in:
item sales
---- -----
a 10
b 9
SQL Fiddle

SQL query to return only 1 record per group ID

I'm looking for a way to handle the following scenario. I have a database table that I need to return only one record for each "group id" that is contained within the table, furthermore the record that is selected within each group should be the oldest person in the household.
ID Group ID Name Age
1 134 John Bowers 37
2 134 Kerri Bowers 33
3 135 John Bowers 44
4 135 Shannon Bowers 42
So in the sample data provided above I would need ID 1 and 3 returned, as they are the oldest people within each group id.
This is being queried against a SQL Server 2005 database.
SELECT t.*
FROM (
SELECT DISTINCT groupid
FROM mytable
) mo
CROSS APPLY
(
SELECT TOP 1 *
FROM mytable mi
WHERE mi.groupid = mo.groupid
ORDER BY
age DESC
) t
or this:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY groupid ORDER BY age DESC) rn
FROM mytable
) x
WHERE x.rn = 1
This will return at most one record per group even in case of ties.
See this article in my blog for performance comparisons of both methods:
SQL Server: Selecting records holding group-wise maximum
Use:
SELECT DISTINCT
t.groupid,
t.name
FROM TABLE t
JOIN (SELECT t.groupid,
MAX(t.age) 'max_age'
FROM TABLE t
GROUP BY t.groupid) x ON x.groupid = t.groupid
AND x.max_age = t.age
So what if there's 2+ people with the same age for a group? It'd be better to store the birthdate rather than age - you can always calculate the age for presentation.
Try this (assuming Group is synonym for Household)
Select * From Table t
Where Age = (Select Max(Age)
From Table
Where GroupId = t.GroupId)
If there are two or more "oldest" people in some household (They all are the same age and there is noone else older), then this will return all of them, not just one at random.
If this is an issue, then you need to add another subquery to return an arbitrary key value for one person in that set.
Select * From Table t
Where Id =
(Select Max(Id) Fom Table
Where GroupId = t.GroupId
And Age =
(Select(Max(Age) From Table
Where GroupId = t.GroupId))
SELECT GroupID, Name, Age
FROM table
INNER JOIN
(
SELECT GroupID, MAX(Age) AS OLDEST
FROM table
) AS OLDESTPEOPLE
ON
table.GroupID = OLDESTPEOPLE.GroupID
AND
table.Age = OLDESTPEOPLE.OLDEST
SELECT GroupID, Name, Age
FROM table
INNER JOIN
(
SELECT GroupID, MAX(Age) AS OLDEST
FROM table
**GROUP BY GroupID**
) AS OLDESTPEOPLE
ON
table.GroupID = OLDESTPEOPLE.GroupID
AND
table.Age = OLDESTPEOPLE.OLDEST