Get the top row after order by in Oracle Subquery - sql

I have a table student(id, name, department, age, score). I want to find the youngest student who has the highest(among the youngest students) score of each department. In SQL Server, I can use following SQL.
select * from student s1
where s1.id in
(select s2.id from student s2
where s2.department = s1.department order by age asc, score desc top 1).
However, in Oracle, you cannot use the order by clause in subquery and there is no limit/top like keyword. I have to join the student table with itself two times to query the result. In oracle, I use following SQL.
select s1.* from student s1,
(select s2.department, s2.age, max(s2.score) as max_score from student s2,
(select s3.department, min(s3.age) as min_age from student s3 group by s3.department) tmp1 where
s2.department = tmp1.department and s2.age = tmp1.min_age group by s2.department, s2.age) tmp2
where s1.department =tmp2.department and s1.age = tmp2.age and s1.score=tmp2.max_score
Does anyone have any idea to simplify the above SQL for oracle.

try this one
select * from
(SELECT id, name, department, age, score,
ROW_NUMBER() OVER (partition by department order by age desc, score asc) srlno
FROM student)
where srlno = 1;

In addition to Allan's answer, this works fine too:
select *
from (SELECT *
FROM student
order by age asc,
score desc)
where rownum = 1;

In addition to Bharat's answer, it is possible to do this using ORDER BY in the sub-query in Oracle (as point out by Jeffrey Kemp):
SELECT *
FROM student s1
WHERE s1.id IN (SELECT id
FROM (SELECT id, ROWNUM AS rn
FROM student s2
WHERE s1.department = s2.department
ORDER BY age ASC, score DESC)
WHERE rn = 1);
If you use this method, you may be tempted to remove the sub-query and just use rownum = 1. This would result in the incorrect result as the sort would be applied after the criteria (you'd get 1 row that was sorted, not one row from the sorted set).

select to_char(job_trigger_time,'mm-dd-yyyy') ,job_status from
(select * from kdyer.job_instances ji INNER JOIN kdyer.job_param_values pm
on((ji.job_id = pm.job_id) and (ji.job_spec_id = '10003') and (pm.param_value='21692') )
order by ji.job_trigger_time desc)
where rownum<'2'

Related

Table's alias created in FROM clause isn't recognized in WHERE clause

The task is "Find the university Id where the number of employees
graduated from that university is maximum over all university"
The schemas is like this
Graduate ( EmpId: NUMERIC REFERENCES Employee(EmpId),
UnivId: NUMERIC REFERENCES University(UnivId),
GradYear: NUMERIC)
University ( UnivId: NUMERIC, UnivName: VARCHAR(40))
Employee (EmpId: NUMERIC,
EmpName: VARCHAR(40))
My query:
SELECT Temp.UnivId
FROM (SELECT G.UnivId, COUNT(*) as Num
FROM Graduate G, Employee E
WHERE G.EmpId = E.EmpId
GROUP BY G.UnivId) AS Temp
WHERE Temp.Num = (SELECT MAX(Temp.Num) FROM Temp);
When I run this query in psql console and the software is PostgresSQL, it return an error say relation "temp" does not exist and it point the Temp at the very end. Does anyone knows why ?
You should be using RANK here:
WITH cte AS (
SELECT g.UnivId, RANK() OVER (ORDER BY COUNT(e.EmpId) DESC) rnk
FROM Graduate g
INNER JOIN Employee e
ON g.EmpId = e.EmpId
GROUP BY g.UnivId
)
SELECT UnivId
FROM cte
WHERE rnk = 1;
Note that this approach also handles ties nicely, should they occur.
The problem with your current approach is that you are referring to the subquery in the WHERE clause as if it's a standalone table, which it is not. You could move the Temp subquery to a CTE, and then your approach can be made to work:
WITH Temp AS (
SELECT G.UnivId, COUNT(*) as Num
FROM Graduate G, Employee E
WHERE G.EmpId = E.EmpId
GROUP BY G.UnivId
)
SELECT Temp.UnivId
FROM Temp
WHERE Temp.Num = (SELECT MAX(Temp.Num) FROM Temp);
You can access columns of the query you have aliased Temp, but you cannot select from it, because you have not created a view. If you want to create an ad-hoc view, use a WITH clause for this.
You should not use comma separated joins by the way. This was the syntax used in the 1980s and some years on until explicit joins ([INNER] JOIN, LEFT [OUTER] JOIN, etc.) made it into the SQL standard in 1992. Why join the employee table anyway?
Here is one way to solve this:
select univid, count(*)
from graduate
group by univid
order by count(*) desc
fetch first row with ties;
Here is another:
select univid, cnt
from
(
select univid, count(*) as cnt, max(count(*)) over () as max_cnt
from graduate
group by univid
) t
where cnt = max_cnt;
And here is what you tried:
with t as
(
select univid, count(*) as cnt
from graduate
group by univid
)
select *
from t
where cnt = (select max(cnt) from t);
It should be simpler. Move your subquery into a CTE then order by num descending and pick the topmost result. You do not need to join Graduate and Employee too.
Btw temp is a reserved word so better do not use it as an identifier/name.
with tmp as
(
select univid, count(*) as num
from graduate
group by univid
)
select univid
from tmp
order by num desc limit 1;
I think that CTEs make SQL code more readable yet you can write the same without a CTE.
select univid
from
(
select univid, count(*) as num
from graduate
group by univid
) tmp
order by num desc limit 1;
However if ties are an issue you better use the rank approach of #TimBiegeleisen, still w/o the join.

Order highest to lowest value without using MAX and ORDER BY

I wanted to get the maximum count of a column for which I used Count() and wanted to order the column highest to lowest value without using max or order by desc.
I tried
SELECT COUNT(subject) AS CNT, student
FROM AUTHOR
GROUP BY student AU1
WHERE not exists (SELECT * FROM AUTHOR AU2
WHERE AU2.student <> AU1.student AND AU2.subject > AU1.CNT)
but it doesn't return the desired output.
The desired output is the same as
SELECT COUNT(subject) AS CNT, student
FROM AUTHOR
GROUP BY Student
ORDER BY CNT DESC
but, without the order by desc or MAX.
You can use ORDER BY ASC:
SELECT COUNT(subject) AS CNT, student
FROM AUTHOR
GROUP BY Student
ORDER BY (- CNT) ASC;
If you want the results in a particular order, you need to use ORDER BY. That is one of the rules of using SQL.
OK, so DESC is not allowed and MAX is not allowed.
SELECT
COUNT(subject) AS CNT, student
FROM
AUTHOR
GROUP BY
Student
ORDER BY
-CNT
This does not work in each database software.
An other version:
SELECT * FROM (
SELECT
COUNT(subject) AS CNT, student
FROM
AUTHOR
GROUP BY
Student
) t
ORDER BY
-CNT

Select rows of joined values using MAX

Consider the following schema.
Student:
StudentID uniqueidentifier
Name varchar(max)
FKTeacherID uniqueidentifier
TestScore:
TestScoreID uniqueidentifier
Score int
FKStudentID uniqueidentifier
My goal is to write a query that yields each teacher's highest test score and the student that achieved it. Returning the teacher's id (Student.FKTeacherID), the score that was achieved (TestScore.Score) and the student that achieved it (Student.Name).
I can write something like this to get the first two required columns:
SELECT FKTeacherID, MAX(Score) MaxScore
FROM Student
JOIN TestScore on FKStudentID = StudentID
GROUP BY FKTeacherID
But I can't obtain the relevant Student.Name without adding it to the group by clause, which would change the result set.
If I'm understanding correctly, one option is to use row_number() -- here's an example with a common-table-expression:
with cte as (
select s.fkteacherid,
ts.score,
s.name,
row_number() over (partition by s.fkteacherid order by ts.score desc) rn
from student s
inner join testscore ts on s.studentid = ts.fkstudentid
)
select fkteacherid, score, name
from cte
where rn = 1
The basic idea is to group by fkteacherid, ordering by the score desc within each group, and taking the first record from each group.
UPDATE
Please try the updated query if you might:
SELECT
FKTeacherID, Name, Score
FROM
Student
JOIN TestScore on FKStudentID = StudentID
JOIN
(
SELECT
B.FKTeacherID AS TeacherID, MAX(A.Score) MaxScore
FROM
Student B
JOIN TestScore A on A.FKStudentID = B.StudentID
GROUP BY
B.FKTeacherID
) As TeachersMaxScore
ON TeachersMaxScore.TeacherID = FKTeacherID
AND TeachersMaxScore.MaxScore = Score

Sum of Highest 5 numbers in SQL Server 2000

I am having a problem in query some data from database. My table is given below:
What i need is that sum of 5 highest total_marks from the table for each student.
Although i tried the code given below, but it is not returning what i expected.
SELECT s.studentid, SUM(s.total_marks)
FROM students s
WHERE s.sub_code IN (SELECT TOP 5 sub_code
FROM students a
WHERE a.studentid = s.studentid
ORDER BY total_marks DESC)
GROUP BY studentid
Please help me guys. Thanking you advance.
You query could work if there's unique/primary key on (studentId, subcode). At the moment, the query returns 6 records instead of 5 for studentId = 1, for example, beause of duplicate subcode 303.
Usually table should have a unique key, may be you can add incremental id to rewrite your query like:
select s.*
from students as s
where
s.id in (
select top 5 a.id
from students as a
where a.studentId = s.studentId
order by a.total_marks desc
);
Or, if you have unique combinations of (studentId, subcode, total_marks), you can use query like this:
select s.*
from students as s
where
exists (
select *
from (
select top 5 a.subcode, a.total_marks
from students as a
where a.studentId = s.studentId
order by a.total_marks desc
) as b
where b.subcode = s.subcode and b.total_marks = s.total_marks
);
sql fiddle demo
First you should select top 5 grades for each student -
select row_number() over (partition by studentid order by total_marks desc) as rank,
studentid,
total_marks
from students
where rank <= 5
from there you'll be able to use this as a subquery, and use group_by and sum:
select studentid, sum(total_marks)
from
(
select row_number() over (partition by studentid order by total_marks desc) as rank,
studentid,
total_marks
from students
where rank <= 5
) t
group by studentid
This isn't ideal, but the method you started to use requires a primary key column. You can simulate one with a temp table since SQL 2000.
CREATE TABLE #temp (
StudentID INT,
total_marks INT,
ID INT Identity(1,1)
)
INSERT INTO #temp (
StudentID,
total_Marks
)
Select
StudentID,
total_marks
FROM Students
SELECT s.studentid, SUM(s.total_marks)
FROM #temp s
WHERE s.ID IN (SELECT TOP 2
a.ID
FROM #temp a
WHERE a.studentid = s.studentid
ORDER BY total_marks DESC)
GROUP BY studentid
I think SQL 2000 may have a slightly more compact syntax for this, but SQL Fiddle won't let me test versions that old.
Please test this carefully. You will be dumping this entire table to a temp table and that's almost always a bad idea.
Also, ensure that there is some combination of fields not including the total that uniquely identifies a row, or consider adding a surrogate key column to the table.
SQL Fiddle Demo

Column is invalid error when using derived table

I'm using ROW_NUMBER() and a derived table to fetch data from the derived table result.
However, I get the error message telling me I don't have the appropriate columns in the GROUP BY clause.
Here's the error:
Column 'tblCompetition.objID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause
What column am I missing? Or am I doing something else wrong? Find below the query that is not working, and the (more simple) query that is working.
SQL Server 2008.
Query that isn't working:
SELECT
objID,
objTypeID,
userID,
datAdded,
count,
sno
FROM
(
SELECT scc.objID,scc.objTypeID,scc.userID,scc.datAdded,
COUNT(sci.favID) as count,
ROW_NUMBER() OVER(PARTITION BY scc.userID ORDER BY scc.unqID DESC) as sno
FROM tblCompetition scc
LEFT JOIN tblFavourites sci
ON sci.favID = scc.objID
AND sci.datTimeStamp BETWEEN #datStart AND #datEnd
) as t
WHERE sno <= 2 AND objTypeID = #objTypeID
AND datAdded BETWEEN #datStart AND #datEnd
GROUP BY objID,objTypeID,userID,datAdded,count,sno
Simple query that is working:
SELECT objId,objTypeID,userId,datAdded FROM
(
SELECT objId,objTypeID,userId,datAdded,
ROW_NUMBER() OVER(PARTITION BY userId ORDER BY unqid DESC) as sno
FROM tblRdbCompetition
) as t
WHERE sno<=2 AND objtypeid=#objTypeID
AND datAdded BETWEEN #datStart AND #datEnd
Thank you!
you need the GROUP BY in your subquery since that's where the aggregate is:
SELECT
objID,
objTypeID,
userID,
datAdded,
count,
sno
FROM
(
SELECT scc.objID,scc.objTypeID,scc.userID,scc.datAdded,
COUNT(sci.favID) as count,
ROW_NUMBER() OVER(PARTITION BY scc.userID ORDER BY scc.unqID DESC) as sno
FROM tblCompetition scc
LEFT JOIN tblFavourites sci
ON sci.favID = scc.objID
AND sci.datTimeStamp BETWEEN #datStart AND #datEnd
GROUP BY scc.objID,scc.objTypeID,scc.userID,scc.datAdded) as t
WHERE sno <= 2 AND objTypeID = #objTypeID
AND datAdded BETWEEN #datStart AND #datEnd
You cannot have count in a group by clause. Infact the count is derived when you have other fields in group by. Remove count from your Group by.
In the innermost query you are using
COUNT(sci.favID) as count,
which is an aggregate, and you select other non-aggregating columns along with it.
I believe you wanted an analytic COUNT instead:
SELECT objID,
objTypeID,
userID,
datAdded,
count,
sno
FROM (
SELECT scc.objID,scc.objTypeID,scc.userID,scc.datAdded,
COUNT(sci.favID) OVER (PARTITION BY scc.userID ) AS count,
ROW_NUMBER() OVER (PARTITION BY scc.userID ORDER BY scc.unqID DESC) as sno
FROM tblCompetition scc
LEFT JOIN
tblFavourites sci
ON sci.favID = scc.objID
AND sci.datTimeStamp BETWEEN #datStart AND #datEnd
) as t
WHERE sno = 1
AND objTypeID = #objTypeID