How to print the students name in this query? - sql

The concerned tables are as follows:
students(rollno, name, deptcode)
depts(deptcode, deptname)
course(crs_rollno, crs_name, marks)
The query is
Find the name and roll number of the students from each department who obtained
highest total marks in their own department.
Consider:
i) Courses of different department are different.
ii) All students of a particular department take same number and same courses.
Then only the query makes sense.
I wrote a successful query for displaying the maximum total marks by a student in each department.
select do.deptname, max(x.marks) from students so
inner join depts do
on do.deptcode=so.deptcode
inner join(
select s.name as name, d.deptname as deptname, sum(c.marks) as marks from students s
inner join crs_regd c
on s.rollno=c.crs_rollno
inner join depts d
on d.deptcode=s.deptcode
group by s.name,d.deptname) x
on x.name=so.name and x.deptname=do.deptname group by do.deptname;
But as mentioned I need to display the name as well. Accordingly if I include so.name in select list, I need to include it in group by clause and the output is as below:
Kendra Summers Computer Science 274
Stewart Robbins English 80
Cole Page Computer Science 250
Brian Steele English 83
expected output:
Kendra Summers Computer Science 274
Brian Steele English 83
Where is the problem?

I guess this can be easily achieved if you use window function -
select name, deptname, marks
from (select s.name as name, d.deptname as deptname, sum(c.marks) as marks,
row_number() over(partition by d.deptname order by sum(c.marks) desc) rn
from students s
inner join crs_regd c on s.rollno=c.crs_rollno
inner join depts d on d.deptcode=s.deptcode
group by s.name,d.deptname) x
where rn = 1;

To solve the problem with a readable query I had to define a couple of views:
total_marks: For each student the sum of their marks
create view total_marks as select s.deptcode, s.name, s.rollno, sum(c.marks) as total from course c, students s where s.rollno = c.crs_rollno group by s.rollno;
dept_max: For each department the highest total score by a single student of that department
create view dept_max as select deptcode, max(total) max_total from total_marks group by deptcode;
So I can get the desidered output with the query
select a.deptcode, a.rollno, a.name from total_marks a join dept_max b on a.deptcode = b.deptcode and a.total = b.max_total
If you don't want to use views you can replace their selects on the final query, which will result in this:
select a.deptcode, a.rollno, a.name
from
(select s.deptcode, s.name, s.rollno, sum(c.marks) as total from course c, students s where s.rollno = c.crs_rollno group by s.rollno) a
join (select deptcode, max(total) max_total from (select s.deptcode, s.name, s.rollno, sum(c.marks) as total from course c, students s where s.rollno = c.crs_rollno group by s.rollno) a_ group by deptcode) b
on a.deptcode = b.deptcode and a.total = b.max_total
Which I'm sure it is easily improvable in performance by someone more skilled then me...
If you (and anybody else) want to try it the way I did, here is the schema:
create table depts ( deptcode int primary key auto_increment, deptname varchar(20) );
create table students ( rollno int primary key auto_increment, name varchar(20) not null, deptcode int, foreign key (deptcode) references depts(deptcode) );
create table course ( crs_rollno int, crs_name varchar(20), marks int, foreign key (crs_rollno) references students(rollno) );
And here all the entries I inserted:
insert into depts (deptname) values ("Computer Science"),("Biology"),("Fine Arts");
insert into students (name,deptcode) values ("Turing",1),("Jobs",1),("Tanenbaum",1),("Darwin",2),("Mendel",2),("Bernard",2),("Picasso",3),("Monet",3),("Van Gogh",3);
insert into course (crs_rollno,crs_name,marks) values
(1,"Algorithms",25),(1,"Database",28),(1,"Programming",29),(1,"Calculus",30),
(2,"Algorithms",24),(2,"Database",22),(2,"Programming",28),(2,"Calculus",19),
(3,"Algorithms",21),(3,"Database",27),(3,"Programming",23),(3,"Calculus",26),
(4,"Zoology",22),(4,"Botanics",28),(4,"Chemistry",30),(4,"Anatomy",25),(4,"Pharmacology",27),
(5,"Zoology",29),(5,"Botanics",27),(5,"Chemistry",26),(5,"Anatomy",25),(5,"Pharmacology",24),
(6,"Zoology",18),(6,"Botanics",19),(6,"Chemistry",22),(6,"Anatomy",23),(6,"Pharmacology",24),
(7,"Sculpture",26),(7,"History",25),(7,"Painting",30),
(8,"Sculpture",29),(8,"History",24),(8,"Painting",30),
(9,"Sculpture",21),(9,"History",19),(9,"Painting",25) ;
Those inserts will load these data:
select * from depts;
+----------+------------------+
| deptcode | deptname |
+----------+------------------+
| 1 | Computer Science |
| 2 | Biology |
| 3 | Fine Arts |
+----------+------------------+
select * from students;
+--------+-----------+----------+
| rollno | name | deptcode |
+--------+-----------+----------+
| 1 | Turing | 1 |
| 2 | Jobs | 1 |
| 3 | Tanenbaum | 1 |
| 4 | Darwin | 2 |
| 5 | Mendel | 2 |
| 6 | Bernard | 2 |
| 7 | Picasso | 3 |
| 8 | Monet | 3 |
| 9 | Van Gogh | 3 |
+--------+-----------+----------+
select * from course;
+------------+--------------+-------+
| crs_rollno | crs_name | marks |
+------------+--------------+-------+
| 1 | Algorithms | 25 |
| 1 | Database | 28 |
| 1 | Programming | 29 |
| 1 | Calculus | 30 |
| 2 | Algorithms | 24 |
| 2 | Database | 22 |
| 2 | Programming | 28 |
| 2 | Calculus | 19 |
| 3 | Algorithms | 21 |
| 3 | Database | 27 |
| 3 | Programming | 23 |
| 3 | Calculus | 26 |
| 4 | Zoology | 22 |
| 4 | Botanics | 28 |
| 4 | Chemistry | 30 |
| 4 | Anatomy | 25 |
| 4 | Pharmacology | 27 |
| 5 | Zoology | 29 |
| 5 | Botanics | 27 |
| 5 | Chemistry | 26 |
| 5 | Anatomy | 25 |
| 5 | Pharmacology | 24 |
| 6 | Zoology | 18 |
| 6 | Botanics | 19 |
| 6 | Chemistry | 22 |
| 6 | Anatomy | 23 |
| 6 | Pharmacology | 24 |
| 7 | Sculpture | 26 |
| 7 | History | 25 |
| 7 | Painting | 30 |
| 8 | Sculpture | 29 |
| 8 | History | 24 |
| 8 | Painting | 30 |
| 9 | Sculpture | 21 |
| 9 | History | 19 |
| 9 | Painting | 25 |
+------------+--------------+-------+
I take chance to point out that this database is badly designed. This becomes evident with course table. For these reasons:
The name is singular
This table does not represent courses, but rather exams or scores
crs_name should be a foreign key referencing the primary key of another table (that would actually represent the courses)
There is no constrains to limit the marks to a range and to avoid a student to take twice the same exam
I find more logical to associate courses to departments, instead of student to departments (this way also would make these queries easier)
I tell you this because I understood you are learning from a book, so unless the book at one point says "this database is poorly designed", do not take this exercise as example to design your own!
Anyway, if you manually resolve the query with my data you will come to this results:
+----------+--------+---------+
| deptcode | rollno | name |
+----------+--------+---------+
| 1 | 1 | Turing |
| 2 | 6 | Bernard |
| 3 | 8 | Monet |
+----------+--------+---------+
As further reference, here the contents of the views I needed to define:
select * from total_marks;
+----------+-----------+--------+-------+
| deptcode | name | rollno | total |
+----------+-----------+--------+-------+
| 1 | Turing | 1 | 112 |
| 1 | Jobs | 2 | 93 |
| 1 | Tanenbaum | 3 | 97 |
| 2 | Darwin | 4 | 132 |
| 2 | Mendel | 5 | 131 |
| 2 | Bernard | 6 | 136 |
| 3 | Picasso | 7 | 81 |
| 3 | Monet | 8 | 83 |
| 3 | Van Gogh | 9 | 65 |
+----------+-----------+--------+-------+
select * from dept_max;
+----------+-----------+
| deptcode | max_total |
+----------+-----------+
| 1 | 112 |
| 2 | 136 |
| 3 | 83 |
+----------+-----------+
Hope I helped!

Try the following query
select a.name, b.deptname,c.marks
from students a
, crs_regd b
, depts c
where a.rollno = b.crs_rollno
and a.deptcode = c.deptcode
and(c.deptname,b.marks) in (select do.deptname, max(x.marks)
from students so
inner join depts do
on do.deptcode=so.deptcode
inner join (select s.name as name
, d.deptname as deptname
, sum(c.marks) as marks
from students s
inner join crs_regd c
on s.rollno=c.crs_rollno
inner join depts d
on d.deptcode=s.deptcode
group by s.name,d.deptname) x
on x.name=so.name
and x.deptname=do.deptname
group by do.deptname
)
Inner/Sub query will fetch the course name and max marks and the outer query gets the corresponding name of the student.
try and let know if you got the desired result

Dense_Rank() function would be helpful in this scenario:
SELECT subquery.*
FROM (SELECT Student_Total_Marks.rollno,
Student_Total_Marks.name,
Student_Total_Marks.deptcode, depts.deptname,
rank() over (partition by deptcode order by total_marks desc) Student_Rank
FROM (SELECT Stud.rollno,
Stud.name,
Stud.deptcode,
sum(course.marks) total_marks
FROM students stud inner join course course on stud.rollno = course.crs_rollno
GROUP BY stud.rollno,Stud.name,Stud.deptcode) Student_Total_Marks,
dept dept
WHERE Student_Total_Marks.deptcode = dept.deptname
GROUP BY Student_Total_Marks.deptcode) subquery
WHERE suquery.student_rank = 1

Related

How to get additional column with row count using two different tables?

In my database I have usergroup, usergroup_user tables. I want to make a SQL query which can result something like result(group_id, name, date, users_count).
usergroup table
------------------
| group_id| name |
------------------
| 10 |test1 |
| 11 |test2 |
| 12 |test3 |
------------------
usergroup_user table
---------------------
|group_id | user_id |
---------------------
| 10 | 100 |
| 10 | 200 |
| 10 | 250 |
| 11 | 250 |
| 11 | 700 |
---------------------
I want to get this kind of a reusult
------------------------------
|group_id | name |users_count|
------------------------------
| 10 |test1 | 3 |
| 11 |test2 | 2 |
| 12 |test3 | 0 |
------------------------------
You simply do this with the group by, as per the following bellow.
SELECT U.group_id ,U.RoleName,COUNT(R.Id)USERCOUNT
FROM usergroup U
LEFT OUTER JOIN usergroup_user R ON R.group_id =U.group_id
GROUP BY U.group_id ,U.RoleName
select
group_id,
name,
(select count(user_id) from [dbo].[usergroup_user] where usergroup_user.group_id=[usergroup].group_id ) AS users_count
from [dbo].[usergroup]
SELECT usergroup.group_id ,usergroup.name,COUNT(usergroup_user.id) as users_count
FROM usergroup
LEFT OUTER JOIN usergroup_user ON usergroup.group_id =usergroup_user.group_id
GROUP BY usergroup.group_id ,usergroup.name

SQL - joining 3 tables and choosing newest logged entry per id

I got rather complicated riddle to solve. So far I'm unlocky.
I got 3 tables which I need to join to get the result.
Most important is that I need highest h_id per p_id. h_id is uniqe entry in log history. And I need newest one for given point (p_id -> num).
Apart from that I need ext and name as well.
history
+----------------+---------+--------+
| h_id | p_id | str_id |
+----------------+---------+--------+
| 1 | 1 | 11 |
| 2 | 5 | 15 |
| 3 | 5 | 23 |
| 4 | 1 | 62 |
+----------------+---------+--------+
point
+----------------+---------+
| p_id | num |
+----------------+---------+
| 1 | 4564 |
| 5 | 3453 |
+----------------+---------+
street
+----------------+---------+-------------+
| str_id | ext | name |
+----------------+---------+-------------+
| 15 | | Mein st. 33 | - bad name
| 11 | | eck st. 42 | - bad name
| 62 | abc | Main st. 33 |
| 23 | efg | Back st. 42 |
+----------------+---------+-------------+
EXPECTED RESULT
+----------------+---------+-------------+-----+
| num | ext | name |h_id |
+----------------+---------+-------------+-----+
| 3453 | efg | Back st. 42 | 3 |
| 4564 | abc | Main st. 33 | 4 |
+----------------+---------+-------------+-----+
I'm using Oracle SQL. Tried using query below but result is not true.
SELECT num, max(name), max(ext), MAX(h_id) maxm FROM history
INNER JOIN street on street.str_id = history._str_id
INNER JOIN point on point.p_id = history.p_id
GROUP BY point.num
In Oracle, you can use keep:
SELECT p.num,
MAX(h.h_id) as maxm,
MAX(s.name) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as name,
MAX(s.ext) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as ext
FROM history h INNER JOIN
street s
ON s.str_id = h._str_id INNER JOIN
point p
ON p.p_id = h.p_id
GROUP BY p.num;
The keep syntax allows you to do "first()" and "last()" for aggregations.

SQL select values sum by same ID

here is my table called "Employee"
eID | name |
==============
1 | Mike |
2 | Josh |
3 | Mike |
And table called "Sells"
sID | eID | | price |
=========================
1 | 1 | | 8 |
2 | 3 | | 9 |
3 | 3 | | 5 |
4 | 1 | | 4 |
5 | 1 | | 3 |
This should be my expected result: returns the total income per employee
name | Income |
==================
Mike | 15 |
Josh | 0 |
Mike | 14 |
Actually, I know use the query "SUM...GROUP BY..." to get the incomes of 15 and 14, but I don't know how to get the income of 0 which is not shown on the "Sells" table.
Could someone give me some help? Thanks a lot.
You just need to use a left outer join, so you can get the sum for missing values too. You could use case expression to deal with null values
SELECT e.name,
COALESCE(SUM(price), 0) as Income
FROM employees e
LEFT OUTER JOIN sells s
ON e.eid = s.eid
GROUP BY e.eid, e.name
Edited: case expression is not needed. I put coalesce on the return of sum fuction, in order to deal with missing values (SUM over an empty set returns NULL)

Query to rank rows in groups

I'm using Apache Derby 10.10.
I have a list of participants and would like to calculate their rank in their country, like this:
| Country | Participant | Points | country_rank |
|----------------|---------------------|--------|--------------|
| Australia | Bridget Ciriac | 1 | 1 |
| Australia | Austin Bjorklun | 4 | 2 |
| Australia | Carrol Motto | 7 | 3 |
| Australia | Valeria Seligma | 8 | 4 |
| Australia | Desmond Miyamot | 27 | 5 |
| Australia | Maryjane Digma | 33 | 6 |
| Australia | Kena Elmendor | 38 | 7 |
| Australia | Emmie Hicke | 39 | 8 |
| Australia | Kaitlyn Mund | 50 | 9 |
| Australia | Alisia Vitaglian | 65 | 10 |
| Australia | Anika Bulo | 65 | 11 |
| UK | Angle Ifil | 2 | 1 |
| UK | Demetrius Buelo | 12 | 2 |
| UK | Ermelinda Mell | 12 | 3 |
| UK | Adeline Pee | 21 | 4 |
| UK | Alvera Cangelos | 23 | 5 |
| UK | Keshia Mccalliste | 23 | 6 |
| UK | Alayna Rashi | 24 | 7 |
| UK | Malinda Mcfarlan | 25 | 8 |
| United States | Gricelda Quirog | 3 | 1 |
| United States | Carmina Britto | 5 | 2 |
| United States | Noemi Blase | 6 | 3 |
| United States | Britta Swayn | 8 | 4 |
| United States | An Heidelber | 12 | 5 |
| United States | Maris Padill | 21 | 6 |
| United States | Rachele Italian | 21 | 7 |
| United States | Jacquiline Speake | 28 | 8 |
| United States | Hipolito Elami | 45 | 9 |
| United States | Earl Sayle | 65 | 10 |
| United States | Georgeann Ves | 66 | 11 |
| United States | Conchit Salli | 77 | 12 |
The schema looks like this (sqlfiddle):
create table Country(
id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
name varchar(255),
PRIMARY KEY (id)
);
create table Team(
id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
country_id int not null,
PRIMARY KEY (id),
FOREIGN KEY (country_id) REFERENCES Country(id)
);
create table Participant(
id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
team_id int not null,
name varchar(100),
points int,
PRIMARY KEY (id),
FOREIGN KEY (team_id) REFERENCES Team(id)
);
This is what I have tried:
select
Country.name,
Participant.name,
Participant.points,
ROW_NUMBER() OVER(order by Country.name, Participant.points) as country_rank
from Country
join Team
on Country.id = Team.country_id
join Participant
on Team.id = Participant.team_id;
But according to the apache derby doco, the OVER() statement doesn't take any arguments.
Does anyone have a way to achieve the country rank?
SQL
SELECT c.name AS Country,
p.name AS Participant,
p.points AS Points,
(SELECT COUNT(*)
FROM Participant p2
JOIN Team t2 ON p2.team_id = t2.id
WHERE t2.country_id = t.country_id
AND (p2.points < p.points
OR p2.points = p.points AND p2.name <= p.name)) AS country_rank
FROM Country c
JOIN Team t ON c.id = t.country_id
JOIN Participant p ON t.id = p.team_id
ORDER BY c.name, p.points, p.name;
Online Demo
SQL Fiddle demo: http://sqlfiddle.com/#!5/f48f8/14
Explanation
A simple ANSI-SQL subselect can be used to do the same job, counting the number of records for participants in the same country with a lower score or with the same score and a name that is alphabetically no higher.
Consider a non-windows function SQL query that uses a correlated aggregate count subquery. Because the group column (Country.name) is not in same table as the rank criteria (Participant.points), we need to run same joins in the subquery but rename table aliases to properly compare inner and outer queries.
Now of course, in a perfect world that would be it but we must now account for tied points. Therefore, another very similar subquery (for tie breaker) is used to be added to first subquery. This second nested query matches inner and outer query's Country.name and Participant.points but ranks by alphabetical order of Participant.name.
SELECT
Country.name AS Country,
Participant.name AS Participant,
Participant.points,
(SELECT Count(*) + 1
FROM Country subC
INNER JOIN Team subT
ON subC.id = subT.country_id
INNER JOIN Participant subP
ON subT.id = subP.team_id
WHERE subC.name = Country.name
AND subP.points < Participant.points)
+
(SELECT Count(*)
FROM Country subC
INNER JOIN Team subT
ON subC.id = subT.country_id
INNER JOIN Participant subP
ON subT.id = subP.team_id
WHERE subC.name = Country.name
AND subP.points = Participant.points
AND subP.name < Participant.name) As country_rank
FROM Country
INNER JOIN Team
ON Country.id = Team.country_id
INNER JOIN Participant
ON Team.id = Participant.team_id
ORDER BY Country.name, Participant.points;
all you need to add is a partition by country and that should give you what you need.
SELECT
Country.name,
Participant.name,
Participant.points,
ROW_NUMBER() OVER(PARTITION BY country order by Country.name, Participant.points) as country_rank
from Country
join Team
on Country.id = Team.country_id
join Participant
on Team.id = Participant.team_id;

Crosstab Query with Dynamic Columns in SQL Server 2005 up

I'm having a problem with Crosstab query in SQL Server.
Suppose that I have data as below:
| ScoreID | StudentID | Name | Sex | SubjectName | Score |
------------------------------------------------------------------
| 1 | 1 | Student A | Male | C | 100 |
| 2 | 1 | Student A | Male | C++ | 40 |
| 3 | 1 | Student A | Male | English | 60 |
| 4 | 1 | Student A | Male | Database | 15 |
| 5 | 1 | Student A | Male | Math | 50 |
| 6 | 2 | Student B | Male | C | 77 |
| 7 | 2 | Student B | Male | C++ | 12 |
| 8 | 2 | Student B | Male | English | 56 |
| 9 | 2 | Student B | Male | Database | 34 |
| 10 | 2 | Student B | Male | Math | 76 |
| 11 | 3 | Student C | Female | C | 24 |
| 12 | 3 | Student C | Female | C++ | 10 |
| 13 | 3 | Student C | Female | English | 15 |
| 14 | 3 | Student C | Female | Database | 40 |
| 15 | 3 | Student C | Female | Math | 21 |
| 16 | 4 | Student D | Female | C | 17 |
| 17 | 4 | Student D | Female | C++ | 34 |
| 18 | 4 | Student D | Female | English | 24 |
| 19 | 4 | Student D | Female | Database | 56 |
| 20 | 4 | Student D | Female | Math | 43 |
I want to make query which show the result as below:
| StuID| Name | Sex | C | C++ | Eng | DB | Math | Total | Average |
| 1 | Student A | Male | 100| 40 | 60 | 15 | 50 | 265 | 54 |
| 2 | Student B | Male | 77 | 12 | 56 | 34 | 76 | 255 | 51 |
| 3 | Student C | Female | 24 | 10 | 15 | 40 | 21 | 110 | 22 |
| 4 | Student D | Female | 17 | 34 | 24 | 56 | 43 | 174 | 34.8 |
How could I query to show output like this?
Note:
Subject Name:
C
C++
English
Database
Math
will be changed depend on which subject student learn.
Please go to http://sqlfiddle.com/#!6/2ba07/1 to test this query.
There are two ways to perform a PIVOT static where you hard-code the values and dynamic where the columns are determined when you execute.
Even though you will want a dynamic version, sometimes it is easier to start with a static PIVOT and then work towards a dynamic one.
Static Version:
SELECT studentid, name, sex,[C], [C++], [English], [Database], [Math], total, average
from
(
select s1.studentid, name, sex, subjectname, score, total, average
from Score s1
inner join
(
select studentid, sum(score) total, avg(score) average
from score
group by studentid
) s2
on s1.studentid = s2.studentid
) x
pivot
(
min(score)
for subjectname in ([C], [C++], [English], [Database], [Math])
) p
See SQL Fiddle with demo
Now, if you do not know the values that will be transformed then you can use Dynamic SQL for this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(SubjectName)
from Score
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT studentid, name, sex,' + #cols + ', total, average
from
(
select s1.studentid, name, sex, subjectname, score, total, average
from Score s1
inner join
(
select studentid, sum(score) total, avg(score) average
from score
group by studentid
) s2
on s1.studentid = s2.studentid
) x
pivot
(
min(score)
for subjectname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
Both versions will yield the same results.
Just to round out the answer, if you do not have a PIVOT function, then you can get this result using CASE and an aggregate function:
select s1.studentid, name, sex,
min(case when subjectname = 'C' then score end) C,
min(case when subjectname = 'C++' then score end) [C++],
min(case when subjectname = 'English' then score end) English,
min(case when subjectname = 'Database' then score end) [Database],
min(case when subjectname = 'Math' then score end) Math,
total, average
from Score s1
inner join
(
select studentid, sum(score) total, avg(score) average
from score
group by studentid
) s2
on s1.studentid = s2.studentid
group by s1.studentid, name, sex, total, average
See SQL Fiddle with Demo
You need to use SQL PIVOT in this case. Plese refer the following link:
Pivot on Unknown Number of Columns
Pivot two or more columns in SQL Server
Pivots with Dynamic Columns in SQL Server
This requires building a SQL query string at runtime. Column names, counts and data-types in SQL Server are always static (the most important reason for that is that the optimizer must know the query data flow at optimization time).
So I recommend that you build a PIVOT-query at runtime and run it through sp_executesql. Note that you have to hardcode the pivot-column values. Be careful to escape them properly. You cannot use parameters for them.
Alternatively you can build one such query per column-count and use parameters just for the pivot values. You would have to assign some dummy column names like Pivot0, Pivot1, .... Still you need one query template per count of columns. Except if you are willing to hard-code the maximum number of pivot-columns into the query (say 20). In this case you actually could use static SQL.