How to Select Top 3 Highest Values From Each GROUP - sql

I have a Marks table where Student's Quiz Marks and Assignment marks are entered in the following way
StudentID
CourseID
Quiz1
Quiz2
Quiz3
Quiz4
Quiz5
Assignment1
Assignment2
Assignment3
Assignment4
Assignment5
1
321
10
8
4
1
9
7
3
9
8
5
2
321
6
10
6
3
8
4
7
1
8
6
3
321
7
9
2
8
4
10
7
5
8
3
4
321
7
2
6
4
8
3
6
9
10
5
5
321
3
4
5
7
10
5
7
8
3
9
Now I want to select the highest 3 quiz numbers and the highest 3 assignment numbers and sum them as the total quiz and total assignment numbers respectively for each student. I have bold highest numbers in the above table for a better understanding
The result should be something like this
StudentID
CourseID
QuizTotal
AssignmentTotal
1
321
27
24
2
321
24
21
3
321
24
25
4
321
21
25
5
321
22
24
What I have Tried is unpivot technique but the result is not what I am expecting. Here's what I tried
SELECT TOP(3) StudentID, Marks
FROM
(SELECT StudentID,CourseID , Quiz1, Quiz2, Quiz3, Quiz4, Quiz5 FROM Marks) stu
UNPIVOT
(Marks FOR QuizNo IN (Quiz1, Quiz, Quiz3, Quiz4, Quiz5)) AS mrks
WHERE (CourseID = 321)
Order by Marks Desc

One way is to unpivot and sum marks with CROSS APPLY
SELECT StudentID, CourseID, qm.m, am.m
FROM Marks
cross apply (
select sum (q) m
from (
select top(3) q
from (
values
(Quiz1),(Quiz2),(Quiz3),(Quiz4),(Quiz5)
) t(q)
order by q desc
) tq
) qm
cross apply (
select sum (q) m
from (
select top(3) q
from (
values
(Assignment1),(Assignment2),(Assignment3),(Assignment4),(Assignment5)
) t(q)
order by q desc
) tq
) am

Related

T-SQL How to configure Group by so that specific values would be correctly shown

My current T-SQL query provides the following results:
Query:
WITH CTE AS
(
SELECT SubscriberID, sum(valueMB) as ValuesMB
FROM dbo.InternetNetwork
GROUP BY SubscriberID
),
CTE2 AS (
SELECT ab.planID, a.SubscriberID, MAX(ValuesMB) as MaximumValue
FROM CTE AS a
left join
Subscriber as ab on a.SubscriberID= ab.SubscriberID
GROUP BY ab.planID, a.SubscriberID
)
select *
FROM CTE2 as b
ORDER BY b.MaximumValue desc
Output:
planID | SubscriberID | MaxValue
19 1555 97536.00
18 3528 97478.00
2 4029 93413.00
Query #2:
WITH CTE AS
(
SELECT SubscriberID, sum(valueMB) as ValuesMB
FROM dbo.InternetNetwork
GROUP BY SubscriberID
),
CTE2 AS(
SELECT ab.planID, MAX(ValuesMB) as MaximumValue
FROM CTE AS a
left join
Subscriber as ab on a.SubscriberID= ab.SubscriberID
GROUP BY ab.planID
)
SELECT pl.OperatorID, MAX(b.MaximumValue) as Super
FROM CTE2 as b
left join
Plan as pl on b.planID= pl.planID
GROUP BY pl.operatorID
ORDER BY pl.operatorID
Output #2:
OperatorID | Value
1 93413.00
2 86017.00
3 97536.00
I would like to also include a subscriberID, but I'm unable to figure out a way to do so, as the only way to do it, is including in the last SELECT and adding to GROUP BY, which when done, makes a mess of a result which is not accurate.
My desired output:
OperatorID | Value | SubscriberID
1 93413.00 4029
2 86017.00 164
3 97536.00 1544
internet network data:
SubscriberID ValuesMB
1 28
1 27
2 27
2 27
2 27
3 29
3 28
3 27
3 27
4 27
4 27
4 29
Subscriber Data:
SubscriberID PersonID PlanID
1 1 3
2 2 10
3 2 6
4 3 14
5 3 1
6 4 18
7 5 5
8 5 1
9 5 9
10 5 16
11 6 13
12 6 13
13 6 20
14 6 16
15 7 4
Plan data
PlanID OperatorID
1 1
2 1
3 2
4 2
5 2
6 2
7 2
8 2
9 2
10 2
11 2
12 3
13 3
14 3
15 3
16 3
17 3
18 3
19 3
20 3
The tables are somewhat like this related InternetNetwork-> Subscriber -> Plan. InternetNetwork contains how much each Subscribed has used. Each Subscriber has Plan associated with him. Each Plan contains a different Operator, there are only three. I wish to list all three operators, the data transferred by the subscriber of the plan that has the operator and Subscriber ID.
Window functions allow you to have fields in your select along with aggregate functions. You can do something like this
;WITH CTE AS
(
SELECT I.SubscriberID,
S.PlanID,
SUM(ValuesMB) OVER(PARTITION BY i.SubscriberID)as ValuesMB
FROM dbo.InternetNetwork I
JOIN Subscriber S
ON I.SubscriberID = S.SubscriberID
),
CTE2 AS
(
SELECT p.operatorID,
a.SubscriberID,
a.ValuesMB,
ROW_NUMBER() OVER(PARTITION BY p.operatorID ORDER BY a.ValuesMB DESC) as rn
FROM CTE a
join [Plan] P
on a.planID = P.planID
)
SELECT operatorID,
ValuesMB,
SubscriberID
FROM CTE2
where rn = 1

SELECT COUNT of child records as subquery within UNION

I'm trying to get a count of the number of children associated with a user. The portion of this query that selects the number of children records is: (select count(*) from blitz_user as ua where ua.manager_id= u.id ) as "directsCount".
The query selects the correct number of children for all records except those with zero (0) children. For records with zero children the value of directsCount is still one (1).
The database in use is PostgreSQL. Please let me know if this is easier to understand with sample data. I tried to limit the length of the post in case that is not helpful.
WITH RECURSIVE subordinates AS (
SELECT u.id,
u.company_id as "companyId",
u.manager_id as "managerId",
1 as levels_down,
(select count(*) from blitz_user as ua where ua.manager_id= u.id ) as "directsCount"
FROM blitz_user AS u
WHERE u.manager_id = 9
UNION
SELECT u.id,
u.company_id as "companyId",
u.manager_id as "managerId",
levels_down + 1, 1 as levels_down
FROM blitz_user AS u
INNER JOIN subordinates AS s ON u.manager_id = s.id
WHERE levels_down <= 5
)
SELECT id, "companyId", "managerId", levels_down, "directsCount"
FROM subordinates;
Please see the following dbfiddle for example code along with the current output.
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=509411d5971efc4eddfccabf3c4d22a4
The current output is:
id companyId managerId levels_down directsCount
10 6 9 1 2 (correct)
11 6 9 1 1 (correct)
12 6 9 1 2 (correct)
13 6 9 1 3 (correct)
14 6 13 2 1 (should be 2)
15 6 13 2 1 (should be 2)
16 6 13 2 1 (correct)
41 6 10 2 1 (should be 3)
45 6 10 2 1 (should be 3)
49 6 11 2 1 (should be 4)
54 6 12 2 1 (should be 0)
58 6 12 2 1 (should be 3)
17 6 14 3 1 (should be 4)
<snip>
The problem just repeats at this point for all remaining rows.
The "levels_down" tracks how far down the hierarchy we are. The values for "directsCount" are correct when we are only 1 levels_down, but then default to "1" for all levels_down from 2, 3, etc.

Counting current and longest streaks of a given value

I have 2 tables Person (ID, NAME, CLAN_ID) and DailyScore (PERSON_ID, CLAN_ID, DAY_NUMBER, SCORE).
SCORE can take the values "A", "B", "C" or "-" ("-" means absent).
I need to make 2 separate queries to get, for a given CLAN_ID:
the current streak of a given score (let's say A, e.g.) for each Person and the name of the Person, ordered by streak length DESC
the longest ever streak of a given score (let's say A, e.g.) for each Person and the name of the Person, ordered by streak length DESC
An important constraint is that "-" SCORES are ignored, as they represent absences, not real Scores.
Example data:
Table Person:
_ID NAME CLAN_ID
1 John 11
2 Alice 11
3 Bob 12
4 Sara 12
Table DailyScore:
PERSON_ID CLAN_ID DAY_NUMBER SCORE
1 11 1 A
1 11 2 A
1 11 3 A
1 11 4 C
1 11 5 A
2 11 1 B
2 11 2 C
2 11 3 B
2 11 4 A
2 11 5 A
3 12 1 A
3 12 2 A
3 12 3 A
3 12 4 A
3 12 5 B
4 12 1 C
4 12 2 B
4 12 3 C
4 12 4 A
4 12 5 -
Desired result example 1 (CLAN_ID=11, SCORE=A):
Current streak:
Alice 2
John 1
Longest ever streak:
John 3
Alice 2
Desired result example 2 (CLAN_ID=12, SCORE=A):
Current streak:
Sara 1*
Bob 0
*since "-" are ignored, Sara has a current streak of 1 A score
Longest ever streak:
Bob 4
Sara 1
Edit:
In case it helps, here's this example in SQL Fiddle: http://sqlfiddle.com/#!7/2ed69/2
The first query can be:
select
id, name, max(s) as streak
from (
select
p.id,
p.name,
count(*) over(partition by p.id order by s.day_number desc) as c,
sum(case when s.score = 'A' then 1 else 0 end)
over(partition by p.id order by s.day_number desc) as s
from person p
join dailyscore s on s.person_id = p.id
) x
where c = s
group by id, name

T-SQL get all nodes and their last level children using common table expression

I need the last level children of all nodes between the last level and the parent. I am new to recursive ctes and trying to get this result.
Consider the following table:
emp id | mgr id
1 null
2 1
3 1
4 1
5 2
6 2
7 3
8 3
9 4
10 4
11 5
12 6
13 6
14 6
15 11
if the input is (15) then the output should be
mgr id emp id
2 12
2 13
2 14
2 15
5 15
6 12
6 13
6 14
11 15
Searching around the web I figured this can be done using recursive queries but I am unable to.
with cte as
(
select
emp_id, mgr_id
from
emp_heirarchy
where
emp_id in (select MIN(lt.emp_id)
from landing_table lt
inner join emp_heirarchy eh on lt.emp_id = eh.emp_id
group by mgr_id)
union all
select
eh.emp_id, eh.mgr_id
from
emp_heirarchy eh
inner join
cte c on c.mgr_id = eh.emp_id
)
select distinct *
from cte
order by mgr_id
I have tried a few other versions of the above query with no luck.

Query to serialize data

I have two tables:
Routes
ID Description
1 street1
2 street2
3 street3
4 street4
5 street5
Segments
ID RouteID, Progres, LabelStart, LabelEnd
1 1 5 1 A 21 B
2 1 10 2 A 10
3 2 15 3 25
4 2 15 2 20
5 3 20 1 11
6 3 22 4 10
7 4 30 5 11
8 4 31 2 12
I need a sequence with these rules:
table must be ordered by Progress ASC
A column Type is defined and take O if LabelStart and LabelEnd are Odd, E if Even
if two routes have the same progress then the rows are merged in one where
LabelStart is the minimum (among LabelStart Odd and LabelStart Even)
and LabelEnd is the Max, in this case Type takes the value of A (All)
as per example data above the result should be
Sequence
ID RouteID, Progres, LabelStart, LabelEnd Type
1 1 5 1 A 21 B O
2 1 10 2 A 10 E
4 2 15 2 25 A
5 3 20 1 11 O
6 3 22 4 10 E
7 4 30 5 11 O
8 4 31 2 12 E
It is for Postgres 9.2
This was an interesting query to write because you had letters in your LabelStart and LabelEnd fields. I used REGEX_REPLACE to remove those. Then I used a CTE to get the records that had more than one routeid and progress rows.
I think this should do it:
WITH CTE AS (
SELECT
RouteId, Progress
FROM Sequence
GROUP BY RouteId, Progress
HAVING COUNT(DISTINCT Id) > 1
)
SELECT MAX(S.ID) Id,
T.RouteId,
T.Progress,
MIN(regexp_replace(LabelStart, '[^0-9]', '', 'g')) LabelStart,
MAX(regexp_replace(LabelStart, '[^0-9]', '', 'g')) LabelEnd,
'A' as Type
FROM Sequence S
INNER JOIN CTE T ON S.RouteId = T.RouteId AND S.Progress = T.Progress
GROUP BY T.RouteId, T.Progress
UNION
SELECT S.Id,
S.RouteId,
S.Progress,
S.LabelStart,
S.LabelEnd,
CASE
WHEN CAST(regexp_replace(LabelStart, '[^0-9]', '', 'g') as int) % 2 = 0
THEN 'E'
ELSE 'O'
END
FROM Sequence S
LEFT JOIN CTE T ON S.RouteId = T.RouteId AND S.Progress = T.Progress
WHERE T.RouteId IS NULL
ORDER BY Progress ASC
And some sample Fiddle.