Oracle group by functions - sql

I have a table with the following values.
read_count users manager
----------------------------------
16 Ann Jake
12 Ann Jake
19 Tom Martin
I am trying to group the values based on the manager and take the sum of maximum read_count per user.
something like
select manager,sum(max(read_count)) from table group by manager
(I know this group by doesn't work. Just gave here for better understanding)!

Here is one approach. Use row_number() to enumerate the rows for each user/manager combination in descending order by read_count. Then, use condition sum to get only the one value per user:
select manager,
sum(case when seqnum = 1 then read_count end) as SumMaxReadCountPerUser
from (select t.*,
row_number() over (partition by manager, users order by read_count desc
) as seqnum
from table t
) t
group by manager;
You can also do this with nested group by statements:
select manager, sum(max_read_count)
from (select manager, users, max(read_count) as max_read_count
from table t
group by manager, users
) mu
group by manager;
I prefer the first method because it generalizes more easily, say if you want the sum of the two highest values.

Related

Use window functions to select the value from a column based on the sum of another column, in an aggregate query

Consider this data (View on DB Fiddle):
id
dept
value
1
A
5
1
A
5
1
B
7
1
C
5
2
A
5
2
A
5
2
B
15
2
A
2
The base query I am running is pretty simple. Just get the total value by id and the most frequent dept.
SELECT
id,
MODE() WITHIN GROUP(ORDER BY dept) AS dept_freq,
SUM(value) AS value
FROM test
GROUP BY id
;
id
dept_freq
value
1
A
22
2
A
27
But I also need to get, for each id, the dept that concentrates the greatest value (so the greatest sum of value by id and dept, not the highest individual value in the original table).
Is there any way to use window functions to achieve that and do it directly in the base query above?
The expected output for this particular example would be:
id
dept_freq
dept_value
value
1
A
A
22
2
A
B
27
I could achieve that with the query below and then joining that with the results of the base query above
SELECT * FROM(
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY value DESC) as row
FROM (
SELECT id, dept, SUM(value) AS value
FROM test
GROUP BY id, dept
) AS alias1
) AS alias2
WHERE alias2.row = 1
;
id
dept
value
row
1
A
10
1
2
B
15
1
But it is not easy to read/maintain and seems also pretty inefficient. So I thought it should be possible to achieve this using window functions directly in the base query, and that also may also help Postgres to come up with a better query plan that does less passes over the data. But none of my attempts using over partition and filter worked.
step-by-step demo:db<>fiddle
You can fetch the dept for the highest values using the first_value() partition function. Adding this before your mode() grouping should do it:
SELECT
id,
highest_value_dept,
MODE() WITHIN GROUP(ORDER BY dept) AS dept_freq,
SUM(value) as value
FROM (
SELECT
id,
dept,
value,
FIRST_VALUE(dept) OVER (PARTITION BY id ORDER BY value DESC) as highest_value_dept
FROM test
) s
GROUP BY 1,2

How to show 'the most popular name' in each year using SQL

I am practicing SQL in Google Cloud Platform and have a table with the most popular names in USA.
I need to write a query that returns the most popular name in every year.
The Table has 6 columns: id, state, gender, year, name, Number of occurrences of the name
So far I have:
SELECT DISTINCT year
FROM 'table'
But I don't now what next...
What you are looking for is the called the mode in statistics. One way to get this uses aggregation and window fucntions:
select year, name
from (select year, name, count(*) as cnt,
row_number() over (partition by year order by count(*) desc) as seqnum
from t
group by year, name
) yn
where seqnum = 1;
The above version returns one arbitrary name if there are ties for the most frequent. If you do want ties, use rank() instead of row_number().

SQL Query to obtain the maximum value for each unique value in another column

ID Sum Name
a 10 Joe
a 8 Mary
b 21 Kate
b 110 Casey
b 67 Pierce
What would you recommend as the best way to
obtain for each ID the name that corresponds to the largest sum (grouping by ID).
What I tried so far:
select ID, SUM(Sum) s, Name
from Table1
group by ID, Name
Order by SUM(Sum) DESC;
this will arrange the records into groups that have the highest sum first. Then I have to somehow flag those records and keep only those. Any tips or pointers? Thanks a lot
In the end I'd like to obtain:
a 10 Joe
b 110 Casey
You want the row_number() function:
select id, [sum], name
from (select t.*]
row_number() over (partition by id order by [sum] desc) as seqnum
from table1
) t
where seqnum = 1;
Your question is more confusing than it needs to be because you have a column called sum. You should avoid using SQL reserved words for identifiers.
The row_number() function assigns a sequential number to a group of rows, starting with 1. The group is defined by the partition by clause. In this case, all rows with the same id are in the same group. The ordering of the numbers is determined by the order by clause, so the one with the largest value of sum gets the value of 1.
If you might have duplicate maximum values and you want all of them, use the related function rank() or dense_rank().
select *
from
(
select *
,rn = row_number() over (partition by Id order by sum desc)
from table
)x
where x.rn=1
demo

TSQL - Sum of Top 3 records of multiple teams

I am trying to generate a TSQL query that will take the top 3 scores (out of about 50) for a group of teams, sum the total of just those 3 scores and give me a result set that has just the name of the team, and that total score ordered by the score descending. I'm pretty sure it is a nested query - but for the life of me can't get it to work!
Here are the specifics, there is only 1 table involved....
table = comp_lineup (this table holds a separate record for each athlete in a match)
* athlete
* team
* score
There are many athletes to a match - each one belongs to a team.
Example:
id athlete team score<br>
1 1 1 24<br>
2 2 1 23<br>
3 3 2 21<br>
4 4 2 25<br>
5 5 1 20<br>
Thank You!
It is indeed a subquery, which I often put in a CTE instead just for clarity. The trick is the use of the rank() function.
;with RankedScores as (
select
id,
athlete,
team,
score,
rank() over (partition by team order by score desc) ScoreRank
from
#scores
)
select
Team,
sum(Score) TotalScore
from
RankedScores
where
ScoreRank <= 3
group by
team
order by
TotalScore desc
To get the top n value for every group of data a query template is
Select group_value, sum(value) total_value
From mytable ext
Where id in (Select top *n* id
From mytable sub
Where ext.group_value = sub.group_value
Order By value desc)
Group By group_value
The subquery retrieve only the ID of the valid data for the current group_value, the connection between the two dataset is the Where ext.group_value = sub.group_value part, the WHERE in the main query is used to mask every other ID, like a cursor.
For the specific question the template became
Select team, sum(score) total_score
From mytable ext
Where id in (Select top 3 id
From mytable sub
Where ext.team = sub.team
Order By score desc)
Group By team
Order By sum(score) Desc
with the added Order By in the main query for the descending total score

sql query finding most often level appear

I have a table Student in SQL Server with these columns:
[ID], [Age], [Level]
I want the query that returns each age value that appears in Students, and finds the level value that appears most often. For example, if there are more 'a' level students aged 18 than 'b' or 'c' it should print the pair (18, a).
I am new to SQL Server and I want a simple answer with nested query.
You can do this using window functions:
select t.*
from (select age, level, count(*) as cnt,
row_number() over (partition by age order by count(*) desc) as seqnum
from student s
group by age, level
) t
where seqnum = 1;
The inner query aggregates the data to count the number of levels for each age. The row_number() enumerates these for each age (the partition by with the largest first). The where clause then chooses the highest values.
In the case of ties, this returns just one of the values. If you want all of them, use rank() instead of row_number().
One more option with ROW_NUMBER ranking function in the ORDER BY clause. WITH TIES used when you want to return two or more rows that tie for last place in the limited results set.
SELECT TOP 1 WITH TIES age, level
FROM dbo.Student
GROUP BY age, level
ORDER BY ROW_NUMBER() OVER(PARTITION BY age ORDER BY COUNT(*) DESC)
Or the second version of the query using amount each pair of age and level, and max values of count pair age and level per age.
SELECT *
FROM (
SELECT age, level, COUNT(*) AS cnt,
MAX(COUNT(*)) OVER(PARTITION BY age) AS mCnt
FROM dbo.Student
GROUP BY age, level
)x
WHERE x.cnt = x.mCnt
Demo on SQLFiddle
Another option but will require later version of sql-server:
;WITH x AS
(
SELECT age,
level,
occurrences = COUNT(*)
FROM Student
GROUP BY age,
level
)
SELECT *
FROM x x
WHERE EXISTS (
SELECT *
FROM x y
WHERE x.occurrences > y.occurrences
)
I realise it doesn't quite answer the question as it only returns the age/level combinations where there are more than one level for the age.
Maybe someone can help to amend it so it includes the single level ages aswell in the result set: http://sqlfiddle.com/#!3/d597b/9
with combinations as (
select age, level, count(*) occurrences
from Student
group by age, level
)
select age, level
from combinations c
where occurrences = (select max(occurrences)
from combinations
where age = c.age)
This finds every age and level combination in the Students table and counts the number of occurrences of each level.
Then, for each age/level combination, find the one whose occurrences are the highest for that age/level combination. Return the age and level for that row.
This has the advantage of not being tied to SQL Server - it's vanilla SQL. However, a window function like Gordon pointed out may perform better on SQL Server.