excluding dups which are lower than max values in SQL - 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
)
;

Related

How to group rows with same values in sql and don't disturb after applying order by?

How to group rows with same values in sql and can't disturb after applying order by.
Schema (PostgreSQL v13)
CREATE TABLE IF NOT EXISTS products (
id int NOT NULL,
title varchar(200) NOT NULL,
description varchar(200) NOT NULL,
price int NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO products VALUES
(1, 'test', 'test',2222),
(2, 'test', 'test2',1111),
(3, 'test3', 'test3',1111),
(4, 'test3.2', 'test3.2',555),
(5, 'test3.3', 'test3.3',1111),
(6, 'test4', 'test4 desc',1111);
Query #1
SELECT DISTINCT
tempId,pemId,
title,
description,
(CASE priceno WHEN 1 THEN price ELSE price END )AS price
FROM
(
SELECT
ROW_NUMBER() OVER(PARTITION BY price) AS priceno,
tempId,pemId,title,description,price
FROM (
select id as tempId,id as pemId,title,description,price from products
group by
grouping sets((tempId,price),(tempId,pemId,title,description))
) As b
) As s
order by
tempId, price asc;
tempid
pemid
title
description
price
1
2222
1
1
test
test
2
1111
2
2
test
test2
3
1111
3
3
test3
test3
4
555
4
4
test3.2
test3.2
5
1111
5
5
test3.3
test3.3
6
1111
6
6
test4
test4 desc
Expected Output:
Please suggest any solution in sql. Thank you!
View on DB Fiddle
Schema (PostgreSQL v13)
CREATE TABLE IF NOT EXISTS products (
id int NOT NULL,
title varchar(200) NOT NULL,
description varchar(200) NOT NULL,
price int NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO products VALUES
(1, 'test', 'test',2222),
(2, 'test', 'test2',1111),
(3, 'test3', 'test3',1111),
(4, 'test3.2', 'test3.2',555),
(5, 'test3.3', 'test3.3',1111),
(6, 'test4', 'test4 desc',1111);
Query #1
select id,
title,
description,
(CASE WHEN prno=1 THEN price::varchar ELSE '' END )AS price
from(
select DISTINCT id, title, description, price, prno
from (
select id, 1, null, null, null, price::varchar, 1 as prno from products
union all
select id, 2, id::varchar, title, description, price::varchar, 2 as prno from products
) temp1 (xid, xord, id, title, description,price,prno)
order by price, prno
) as temp2;
id
title
description
price
1111
2
test
test2
6
test4
test4 desc
3
test3
test3
5
test3.3
test3.3
2222
1
test
test
555
4
test3.2
test3.2
View on DB Fiddle

how to combine the result of one column to another

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

Oracle Order By different columns same select statement

I have an interesting problem here. But it is just for knowledge because I already solve it in a non elegant way.
I have a table that have costumers and they can be holders or dependants and this relation is described as a
family. Each family can have only a holder and 0-n dependants. A holder is identified by an H and a Dependant by a D.
What I need is a way to order the data by name of holder and theirs dependants. So the sample data below
idcostumer name idfamily relation
1 Natalie Portman 1 H
2 Mark Twain 3 D
3 Carl Sagan 2 D
4 Bob Burnquist 2 H
5 Sheldon Cooper 1 D
6 Anakin Skywalker 4 H
7 Luke Skywalker 4 D
8 Leia Skywalker 4 D
9 Burnquist Jr. 2 D
10 Micheal Jackson 3 H
11 Sharon Stone 1 H
12 Michelle Pfeiffer 3 D
Is it possible to get the above results in just one query? As you can see the order is name (just for the holders)
idcostumer name idfamily relation
6 Anakin Skywalker 4 H
8 Leia Skywalker 4 D
7 Luke Skywalker 4 D
4 Bob Burnquist 2 H
9 Burnquist Jr. 2 D
3 Carl Sagan 2 D
10 Micheal Jackson 3 H
2 Mark Twain 3 D
12 Michelle Pfeiffer 3 D
11 Sharon Stone 1 H
1 Natalie Portman 1 D
5 Sheldon Cooper 1 D
The test case data for this example.
create table costumer (
idcostumer integer primary key,
name varchar2(20),
idfamily integer,
relation varchar2(1)
);
This is the inserts statments for this table:
insert into costumer values ( 1 , 'Natalie Portman' , 1, 'D');
insert into costumer values ( 2 , 'Mark Twain' , 3, 'D');
insert into costumer values ( 3 , 'Carl Sagan' , 2, 'D');
insert into costumer values ( 4 , 'Bob Burnquist' , 2, 'H');
insert into costumer values ( 5 , 'Sheldon Cooper' , 1, 'D');
insert into costumer values ( 6 , 'Anakin Skywalker' , 4, 'H');
insert into costumer values ( 7 , 'Luke Skywalker' , 4, 'D');
insert into costumer values ( 8 , 'Leia Skywalker' , 4, 'D');
insert into costumer values ( 9 , 'Burnquist Jr.' , 2, 'D');
insert into costumer values ( 10, 'Micheal Jackson' , 3, 'H');
insert into costumer values ( 11, 'Sharon Stone' , 1, 'H');
insert into costumer values ( 12, 'Michelle Pfeiffer', 3, 'D');
I've tried some things, create a father sun relationship with connect by statement and familyid concatenated with the relation.
Used a row_count with a over clause ordering by relation desc and family id, but this way I lost the name order.
If I understand you correctly, you want to first order the families by the name of the holder, then by the names of the dependents. The following does that.
with family_order as (
select idfamily, rownum r from (
select idfamily from costumer where relation = 'H' order by name
)
)
select c.* from costumer c
inner join family_order fo on c.idfamily = fo.idfamily
order by fo.r, relation desc, name
Fiddle here
Try:
select * from table order by idfamily desc, relation desc, name asc
Link to Fiddle
For un-natural order you can use "union all":
select * from (select idcostumer, name, idfamily, relation from costumer
where idfamily > 3
order by idfamily desc, relation desc, name asc)
union all
select * from (
select idcostumer, name, idfamily, relation from costumer
where idfamily = 2
order by idfamily desc, relation desc, name asc)
union all
select * from (
select idcostumer, name, idfamily, relation from costumer
where idfamily != 2 and idfamily < 4
order by idfamily desc, relation desc, name asc)
Link to Fiddle

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

Query to get only the duplicate data

I have a table with data
ID Name
1 John
2 Robert
3 John
4 Sam
5 Jack
6 Sam
Now i want ony the the duplicate names ony through query
ie..,
Name
John
Sam
SELECT Name
FROM YourTable
GROUP BY Name
HAVING COUNT(*) > 1
CREATE TABLE MyTable (
ID int
, Name nvarchar(50)
)
INSERT MyTable VALUES ( 1, 'John' )
INSERT MyTable VALUES ( 2, 'Robert' )
INSERT MyTable VALUES ( 3, 'John' )
INSERT MyTable VALUES ( 4, 'Sam' )
INSERT MyTable VALUES ( 5, 'Jack' )
INSERT MyTable VALUES ( 6, 'Sam' )
SELECT
Name
FROM
MyTable
GROUP BY
Name
HAVING
COUNT(*) > 1
DROP TABLE MyTable
Results:
Name
--------------------------------------------------
John
Sam
with temp as (
select Name, count(Name) as countOfNames
from myTable
group by Name
)
select Name from temp
where countOfNames > 1
select columnname,count(column name) from tablename group by column name having count(*)>1