how to combine the result of one column to another - sql

I am trying to create a student database but i am stuck up the given requirement.
I was given to create 2 tables one with students register number , subjects ans total and another table with student_name , total, rank. i can understand that here we need to use the student name separately and combine it later but the part that got me struck in the student subjects may vary means if student no :1 is having 3 subjects means student no : 2 may have 2 and student no: 3 may have 5 and based on this we have to put ranking order
CREATE TABLE Student
(StudentID int, StudentName varchar(6), Details varchar(1));
INSERT INTO Student
(StudentID, StudentName, Details)
VALUES
(1, 'John', 'X'),
(2, 'Paul', 'X'),
(3, 'George', 'X'),
(4, 'Paul', 'X');
CREATE TABLE Subject
(SubjectID varchar(1), SubjectName varchar(7));
INSERT INTO Subject
(SubjectID, SubjectName)
VALUES
('M', 'Math'),
('E', 'English'),
('H', 'History');
CREATE TABLE Mark
(StudentID int, SubjectID varchar(1), MarkRate int);
INSERT INTO Mark
(StudentID, SubjectID, MarkRate)
VALUES
(1, 'M', 90),
(1, 'E', 100),
(2, 'M', 95),
(2, 'E', 70),
(3, 'E', 95),
(3, 'H', 98),
(4, 'H', 90),
(4, 'E', 100);
I need 2 outputs
1st one is
ID |subjects |marks
----------------------------------------------------------
1 maths 98
1 science 87
1 social 88
2 maths 87
2 english 99
3 maths 96
3 evs 100
3 social 88
3 history 90
and the second table as
NO |name |total|rank
----------------------------------------------------------
1 xxx 123 1
2 yyy 456 2
3 zzz 789 3
I need output like this for n number of entries

1 st one
SELECT
A.StudentID AS [ID],
C.SubjectName AS [subjects],
B.MarkRate AS [marks]
FROM STUDENT A JOIN Mark B ON A.StudentID=B.StudentID JOIN Subject C ON C.SubjectID=B.SubjectID
2nd one
SELECT
A.StudentID AS [NO],
A.StudentName AS [name],
SUM(B.MarkRate) AS [total],
ROW_NUMBER() OVER(ORDER BY SUM(B.MarkRate) DESC) AS [rank]
FROM STUDENT A JOIN Mark B ON A.StudentID=B.StudentID JOIN Subject C ON C.SubjectID=B.SubjectID
GROUP BY A.StudentID,A.StudentName
ORDER BY [total] DESC

select StudentID ,SubjectName ,MarkRate from
#Mark a join #Subject b on a.SubjectID=b.SubjectID
output
StudentID SubjectName MarkRate
1 Math 90
1 English 100
2 Math 95
2 English 70
3 English 95
3 History 98
4 History 90
4 English 100
Second Query
with cte as
(
select a.StudentID,StudentName,sum(MarkRate)MarkRate from #Student a join #Mark B on a.StudentID=b.StudentID
group by a.StudentID,StudentName
)
select *,rank() over( order by MarkRate desc) as rn from cte
output
StudentID StudentName MarkRate rn
3 George 193 1
4 Paul 190 2
1 John 190 2
2 Paul 165 4

To get the Mark list with subject name is simply this :
you need to use JOIN :
SELECT M.StudentId
,SU.SubjectName
,M.MarkRate
FROM Mark M
INNER JOIN Subject SU ON M.SubjectID = SU.SubjectID
To get total marks with ranks you need to use GROUP BY and RANK() function :
SELECT M.StudentId
,ST.StudentName
,SUM(MarkRate) Total
,RANK() OVER(ORDER BY SUM(MarkRate) ) Rank
--,RANK() OVER(ORDER BY SUM(MarkRate) DESC) Rank
FROM Mark M
INNER JOIN Student ST ON M.StudentId = ST.StudentId
GROUP BY M.StudentId
,ST.StudentName

I understand that first query lists the marks of each student
SELECT
m.StudentID as ID,
s.SubjectName as subjects,
m.MarkRate as marks
FROM
Mark m
INNER JOIN Subject s on m.SubjectID = m.SubjectID
ORDER BY
m.StudentID,
s.SubjectName
Second query gives the total mark of Students with their rank.
SELECT
X.StudentID,
X.StudentName,
ROWNUMBER() OVER ( ORDER BY X.TotalMark desc) as Rank
FROM (
SELECT
m.StudentID,
s.StudentName,
sum(m.MarkRate) TotalMark
FROM
Mark m
INNER JOIN Student s on s.StudentID = m.StudentID
GROUP BY
m.StudentID,
s.StudentName
) X
ORDER BY X.TotalMark desc

You can try the following query for both output.
SELECT
Student.StudentID as ID,
[Subject].SubjectName as subjects,
Mark.MarkRate as marks
FROM
Student
INNER JOIN Mark on Student.StudentID = Mark.StudentID
INNER JOIN [Subject] on [Subject].SubjectID = Mark.SubjectID
ORDER BY
Student.StudentID,
SubjectName
Below query for second output. Here as you have said for those who has less mark got Rank 1 otherwise you can put Order By Total Desc in below query for higher marks rank 1.
SELECT ID, StudentName, Total, Row_number() Over (Order BY Total) Ranks FROM(
SELECT Id, StudentName, SUM(marks) as Total FROM (
SELECT
Student.StudentID as ID,
[Subject].SubjectName as subjects,
Mark.MarkRate as marks,
Student.StudentName
FROM
Student
INNER JOIN Mark on Student.StudentID = Mark.StudentID
INNER JOIN [Subject] on [Subject].SubjectID = Mark.SubjectID
)Tot
Group By Id, StudentName
)Ranks

Related

excluding dups which are lower than max values in SQL

I have the following simple table (Table1), where each row is a student_ID and their name, and each student has one or multiple wins (Wins). I would like to output: Student_ID, Student_name, count of Wins, sorted by count of Wins (descending) and then Student_ID (ascending), excluding those students who have the same count of Wins which is less than the max of the Wins (i.e.5). In other words, Lizzy and Mark have the same count of wins, and 3 is lower than 5, so the output will exclude the two students, Lizzy and Mark.
From comments: "Betty, David and Cathy should be excluded", also.
Table1:
student_id
student_name
wins
1
John
YES
1
John
YES
1
John
YES
1
John
YES
1
John
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
3
Lizzy
YES
3
Lizzy
YES
3
Lizzy
YES
4
Mark
YES
4
Mark
YES
4
Mark
YES
5
Betty
YES
6
David
YES
7
Cathy
YES
8
Joe
YES
8
Joe
YES
Desired output:
student_id
student_name
cnt_wins
1
John
5
2
Brandon
5
8
Joe
2
Here is my SQL in Oracle. I can't figure out what went wrong. The log says "(SELECT b.cnt_wins, count(b.student_id) has too many values".
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte b
WHERE b.cnt_wins <
(SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1);
There are too many values selected inside the 'in' select:
WHERE a.cnt_wins -- 1 value
not in
(SELECT b.cnt_wins, count(b.student_id) -- 2 values
FROM st_cte b
you shoud either do :
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte ...
or
WHERE (a.cnt_wins, count(something)) not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte ...
Updated based on updated requirements...
The requirement was ambiguous in that Betty, David, and Cathy seem to also meet the criteria to be removed from the result. This requirement was clarified and those rows should have been removed.
Logic has been added to allow only all max_cnt rows, plus any students with a unique count.
Also note that if wins can be any other non-null value, COUNT(wins) is not correct.
Given all that, maybe something like this is a starting point:
Fiddle
WITH cte AS (
SELECT student_id, student_name
, COUNT(wins) cnt_wins
, MAX(COUNT(wins)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
and to handle wins that can be other non-null values:
WITH cte AS (
SELECT student_id, student_name
, COUNT(CASE WHEN wins = 'YES' THEN 1 END) cnt_wins
, MAX(COUNT(CASE WHEN wins = 'YES' THEN 1 END)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
Result (with data to test the new requirement, one student (Joe) with unique counts (2)):
STUDENT_ID
STUDENT_NAME
CNT_WINS
1
John
5
2
Brandon
5
8
Joe
2
Setup:
CREATE TABLE table1 (
Student_ID int
, Student_Name VARCHAR2(20)
, Wins VARCHAR2(10)
);
BEGIN
-- Assume only wins are stored.
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 5, 'Betty', 'YES');
INSERT INTO table1 VALUES ( 6, 'David', 'YES');
INSERT INTO table1 VALUES ( 7, 'Cathy', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
END;
/
Correction to the original query in the question:
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id
)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte b
WHERE b.cnt_wins < (SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1
)
;

Get column values separated by semi colon

I have two tables in plsql
tblStudent:-
StudentId Name .........
1 A
2 B
tblDept:-
DeptId DeptName StudentId
1 Dep Aero 1
2 IT 1
3 Dep Maths 1
4 Dep Chemistry 2
I want to get studentId, with all its departments which starts with 'Dep' separated by semi colon, If i pass where StudentId = 1 in SELECT result should look like
StudentId DeptName
1 Dep Aero;Dep Maths
any help please?
You may use LISTAGG to concat and LIKE to filter the records.
SELECT studentid,
LISTAGG(deptname,';') WITHIN GROUP(
ORDER BY deptid
) as deptname
FROM t
WHERE deptname LIKE 'Dep%'
GROUP BY studentid;
If you want an empty list of departments when a student has no entries then you can use an outer join between the two tables, e.g.:
select s.studentid,
listagg(d.deptname, ';') within group (order by d.deptname) as deptnames
from tblstudent s
left join tbldept d on d.studentid = s.studentid
and deptname like 'Dep%'
group by s.studentid;
Demo using CTEs for your sample data, including a third student ID with no matching departments:
-- CTEs for sample data
with tblstudent (studentid, name) as (
select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 3, 'C' from dual
),
tbldept (deptid, deptname, studentid) as (
select 1, 'Dep Aero', 1 from dual
union all select 2, 'IT', 1 from dual
union all select 3, 'Dep Maths', 1 from dual
union all select 4, 'Dep Chemistry', 2 from dual
)
-- actual query
select s.studentid,
listagg(d.deptname, ';') within group (order by d.deptname) as deptnames
from tblstudent s
left join tbldept d on d.studentid = s.studentid
and deptname like 'Dep%'
group by s.studentid;
STUDENTID DEPTNAMES
---------- ------------------------------
1 Dep Aero;Dep Maths
2 Dep Chemistry
3
Your data model looks odd though; you should probably have a department table which only has the department IDs and names, and then another tables that links each student to all of their departments - something like (in CTE form again):
-- CTEs for sample data
with tblstudent (studentid, name) as (
select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 3, 'C' from dual
),
tbldept (deptid, deptname) as (
select 1, 'Dep Aero' from dual
union all select 2, 'IT' from dual
union all select 3, 'Dep Maths' from dual
union all select 4, 'Dep Chemistry' from dual
),
tblstudentdept (studentid, deptid) as (
select 1, 1 from dual
union all select 1, 2 from dual
union all select 1, 3 from dual
union all select 2, 4 from dual
)
-- actual query
select s.studentid,
listagg(d.deptname, ';') within group (order by d.deptname) as deptnames
from tblstudent s
left join tblstudentdept sd on sd.studentid = s.studentid
left join tbldept d on d.deptid = sd.deptid
and deptname like 'Dep%'
group by s.studentid;
STUDENTID DEPTNAMES
---------- ------------------------------
1 Dep Aero;Dep Maths
2 Dep Chemistry
3
Either way, if you only want to see a single student's results when add that as a where clause, right before the group by:
...
where s.studentid = 1
group by s.studentid;
Try this GROUP_CONCAT -
SELECT stud2.studentId,
CAST((SELECT GROUP_CONCAT(CONCAT(dep.depName,'; ') FROM tblDept dep
INNER JOIN tblStudent stud ON (stud.DeptId = dep.DeptId)))
FROM tblStudent stud2
No join is necessary for your query. If you want to do this for a particular student id:
select listagg(d.DeptName, ';') within group (order by d.DeptName)
from tblDept d
where d.studentid = :studentid and
d.DeptName like 'Dep%';
you can alo use this:
select
st.studentid,
listagg(d.DeptName,';') within group( order by d.DeptName )
From tblStudent st
join tblDept d on d.studentid = st.studentid
where DeptName like 'Dep%'
group by st.studentid
sqlFiddle

SQL Server Pivot Multiple Tables

We have the following tables:
Table 1: Student_Records
StudentID | CourseID | Period | Grade
12 6010 P1 90
23 6020 P1 80
12 6030 P2 ' ' Blank, there's no grade
15 6010 P1 70
12 6020 P1 80
15 6020 P1 90
Table 2: Course_Records
CourseID CourseDec Credits
6010 Math 3
6020 Biology 3
6030 English 3
Table 3: Student_Info
StudentID FirstName LastName ClassYear
12 Joe Smith 2013
15 Chak Li 2013
23 Pete Vo 2013
Result Desire:
ClassYear LastName FirstName StudentId Math Biology
2013 Smith Joe 12 90 80
2013 Li Chak 15 70 90
How can I achieve this result using the pivot command?
You can use PIVOT for this but it requires that you know which course descriptions you're interested in.
SELECT p.classyear,
p.lastname,
p.firstname,
p.studentid,
pvt.math,
pvt.biology
FROM (SELECT sr.grade,
si.classyear,
si.studentid,
si.firstname,
silastname
FROM student_info si
INNER JOIN student_records sr
ON si.studentid = sr.studentid
INNER JOIN course_records cr
ON sr.courseid = cr.courseid) p PIVOT ( AVG (grade) FOR
coursedec IN (
[Math], [Biology]) ) AS pvt
ORDER BY pvt.classyear;
Query out the numbers and courses with a join so you end up with
StudentID CourseDec Grade
1 Math 20
1 Woodwork 82
Pivot that you end up with
StudentID Math WoodWork
1 20 82
Then join Back to student to get First Name Alst Name etc.
https://data.stackexchange.com/stackoverflow/query/60493/http-stackoverflow-com-questions-9068600-sql-server-pivot-mulitple-tables
DECLARE #Student_Records AS TABLE (
studentid INT,
courseid INT,
period VARCHAR(2),
grade INT);
INSERT INTO #Student_Records
VALUES (12,
6010,
'P1',
90),
(23,
6020,
'P1',
80),
(12,
6030,
'P2',
NULL),
(15,
6010,
'P1',
70),
(12,
6020,
'P1',
80),
(15,
6020,
'P1',
90);
DECLARE #Course_Records AS TABLE (
courseid INT,
coursedec VARCHAR(50),
credits INT);
INSERT INTO #Course_Records
VALUES ( 6010,
'Math',
3),
( 6020,
'Biology',
3),
( 6030,
'English',
3);
DECLARE #Student_Info AS TABLE (
studentid INT,
firstname VARCHAR(50),
lastname VARCHAR(50),
classyear INT);
INSERT INTO #Student_Info
VALUES (12,
'Joe',
'Smith',
2013),
(15,
'Chak',
'Li',
2013),
(23,
'Pete',
'Vo',
2013);
SELECT DISTINCT coursedec
FROM #Course_Records AS cr
INNER JOIN #Student_Records sr
ON sr.courseid = cr.courseid
WHERE sr.grade IS NOT NULL;
SELECT classyear,
lastname,
firstname,
summary.studentid,
summary.math,
summary.biology
FROM (SELECT *
FROM (SELECT si.studentid,
coursedec,
grade
FROM #Course_Records AS cr
INNER JOIN #Student_Records sr
ON sr.courseid = cr.courseid
INNER JOIN #Student_Info si
ON si.studentid = sr.studentid
WHERE sr.grade IS NOT NULL) AS results PIVOT (AVG(grade) FOR
coursedec
IN (
[Math], [Biology])) AS pvt) AS summary
INNER JOIN #Student_Info si
ON summary.studentid = si.studentid
Note that you can use dynamic HTML to make the query adjust as more courses are added:
Pivot Table and Concatenate Columns

Numbering of groups in select (Oracle)

I have a following example - table with name, department and country. I need to create a select statement that lists all records and assigns unique number to each group of department and country (column Group in the example):
Name Department Country Group
====== ============ ========= =====
James HR UK 1
John HR UK 1
Alice Finance UK 2
Bob Finance DE 3
Frank Finance DE 3
I thought of some select with analytic function but I found only row_number() over (partition by department, country) which numbers records inside the group and not groups themselves. Do you have any idea how to solve this problem? Thank you!
SELECT t.*, q.grp
FROM (
SELECT q.*, rownum AS grp
FROM (
SELECT DISTINCT department, country
FROM mytable
ORDER BY
department, country
) q
) q
JOIN mytable t
ON t.department = q.department
AND t.country = q.country
or
SELECT t.*, DENSE_RANK() OVER (ORDER BY department desc, country desc) AS grp
FROM mytable
Its a bit clunky, but you could do a a sub query ( or in this case using the with clause ) on the table to get the distinct department per country then get the rownum from that.
set echo on
DROP TABLE TESTXX
DROP TABLE TESTXX succeeded.
CREATE
TABLE TESTXX
(
NAME VARCHAR2 ( 10 )
, DEPARTMENT VARCHAR2 ( 15 )
, COUNTRY VARCHAR2 ( 2 )
)
CREATE succeeded.
INSERT INTO TESTXX VALUES
( 'James', 'HR', 'UK'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'John', 'HR', 'UK'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'Alice', 'FI', 'UK'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'Bob', 'FI', 'DE'
)
1 rows inserted
INSERT INTO TESTXX VALUES
( 'Frank', 'FI', 'DE'
)
1 rows inserted
.
WITH
X AS
(SELECT
XX.*
, ROWNUM R
FROM
(SELECT
DEPARTMENT
, COUNTRY
FROM
TESTXX
GROUP BY
COUNTRY
, DEPARTMENT
ORDER BY
COUNTRY DESC
, DEPARTMENT DESC
) XX
)
SELECT
T.*
, X.R
FROM
TESTXX T
INNER JOIN X
ON
T.DEPARTMENT = X.DEPARTMENT
AND T.COUNTRY = X.COUNTRY
ORDER BY
T.COUNTRY DESC
, T.DEPARTMENT DESC
NAME DEPARTMENT COUNTRY R
---------- --------------- ------- ----------------------
James HR UK 1
John HR UK 1
Alice FI UK 2
Bob FI DE 3
Frank FI DE 3
5 rows selected

How to get the two best students of each professor in SQL?

I have a table with 3 fields like this:
ProfessorID StudentID Mark
P1 S1 9
P1 S2 8
P1 S3 10
P2 S1 7
P2 S2 2
P2 S3 4
P3 S4 5
P4 S1 6
A professor can teach many students, and vice versa, a student can learn from many professor. When a student learns from a professor, he gets his mark.
My problem is showing list of professors who teach at least 2 students, and 2 students who get best marks from those professors. In example, the query result of this table is:
ProfessorID StudentID Mark
P1 S1 9
P1 S3 10
P2 S1 7
P2 S3 4
I've tried some solutions but they don't work right.
How can I do this correctly?
declare #table table (ProfessorID nvarchar(2), StudentID nvarchar(2),Mark int)
insert into #table
select 'P1', 'S1', 9
union all
select 'P1', 'S2', 8
union all
select 'P1', 'S3', 10
union all
select 'P2', 'S1', 7
union all
select 'P2', 'S2', 2
union all
select 'P2', 'S3', 4
union all
select 'P3', 'S4', 5
union all
select 'P4', 'S1', 6
select *
from #table o
where o.StudentID IN (select top 2 s.StudentID from #table s where s.ProfessorId = o.ProfessorId order by Mark DESC)
and o.ProfessorID IN (select p.ProfessorID from #table p group by p.ProfessorID having count(*) >= 2)
I just woke up... but here goes:
select *
FROM
(select professorID, count(distinct studentID) as studentsTaught
FROM table
) s
WHERE s.studentsTaught = 2
UNION ALL
SELECT top 2 *
FROM table
WHERE professorID IN (select professorID
FROM (
select professorID, count(distinct studentID) as studentsTaught
FROM table
) s
)
WHERE s.studentsTaught = 2
ORDER BY mark
Should work... basically you're querying the professors and counting them as a subquery, then selecting the ones who have 2 students from that subquery. For their students you have to UNION them and find the same professors, but ORDER BY is good enough to get the two best students.
Asuming you want it in SQL 2005 (or 2008), this will work as well
select *
from
(
select *
,rank() over (partition by professorid order by mark desc) as ranking
,(select count(distinct studentid)
from marks m2
where m2.professorid = m1.professorid
group by professorid
) as students
from marks m1
) subs
where ranking < 3
and students > 2
-Edoode
SELECT ProfessorID, StudentID, MAX(Mark)
FROM table
GROUP BY ProfessorID, StudentID