iT邦幫忙

0

C# 面試考題之一 求解法 找出SQL前三排名,同值名稱並列

Ks 2019-05-08 11:03:432392 瀏覽

最近出去面試被這一題考倒了,請問大大這題的SQL該怎麼寫?
/images/emoticon/emoticon03.gif
Student Table column

  1. class 班級
  2. course 課程
  3. studentname 學生名字
  4. score 分數
    找出前三位最大分數,並且同樣分數的學生顯示在同一列,
    例如
    第一 90 學生A
    第二 88 學生B 學生C
    第三 85 學生D
看更多先前的討論...收起先前的討論...
小魚 iT邦高手 1 級 ‧ 2019-05-08 11:06:52 檢舉
話說這東西我根本背不下來,
而且不同的資料庫寫法不一樣吧...
Ks iT邦新手 4 級 ‧ 2019-05-08 11:09:24 檢舉
確實會有不一樣的寫法,那家公司目前是用postgresql,
考了6題錯了1題
louischou iT邦新手 4 級 ‧ 2019-05-08 11:15:52 檢舉
GROUP_CONCAT,查了一下是這個吧?
WQ iT邦新手 4 級 ‧ 2019-05-08 13:10:34 檢舉
SELECT GROUP_CONCAT(學生),分數 FROM table group by 分數 order by 分數 DESC LIMIT 3; <--MYSQL

感謝louischou分享函數

補充一下ORACLE寫法
SELECT * FROM (SELECT 分數, LISTAGG(學生, ',') WITHIN GROUP (ORDER BY 學生) 學生 FROM TABLE GROUP BY 分數 order by 分數 DESC) where ROWNUM<=3
Ks iT邦新手 4 級 ‧ 2019-05-08 13:56:43 檢舉
好的~非常感覺大大分享,剛有搜尋了GROUP_CONCAT,似乎只有mysql可以用
不過還是非常感謝,又學到了一個新用法
優悠 iT邦新手 4 級 ‧ 2019-05-08 17:17:22 檢舉
select top 3 score,(select studentname from table where score=t1.score) from table t1 order by score desc
簡易寫法
Ks iT邦新手 4 級 ‧ 2019-05-08 17:38:33 檢舉
感謝,修改了一下,貌似這樣也可以得出一樣的結果

select distinct t1.score,
(select string_agg(studentname,',') from public.student where score = t1.score)
from public.student as t1
order by score desc
limit 3
5
一級屠豬士
iT邦高手 1 級 ‧ 2019-05-08 16:10:48
最佳解答
-- 使用 PostgreSQL
-- 簡單化 只用一個Table

create table ithelp190508 (
  sname text not null
, score int not null
);

insert into ithelp190508 values
('學生D', 85),('學生B', 88),
('學生C', 88),('學生A', 90);

-- dense_rank()
-- https://www.postgresql.org/docs/current/functions-window.html
select *
     , dense_rank() over (order by score desc)
  from ithelp190508;

+-------+-------+------------+
| sname | score | dense_rank |
+-------+-------+------------+
| 學生A |    90 |          1 |
| 學生B |    88 |          2 |
| 學生C |    88 |          2 |
| 學生D |    85 |          3 |
+-------+-------+------------+
(4 rows)

-- string_agg()
-- https://www.postgresql.org/docs/current/functions-aggregate.html
with t1 as (
select *
     , dense_rank() over (order by score desc) as dns_rnk
  from ithelp190508
)
select dns_rnk
     , score
     , string_agg(sname, ',')
  from t1
 group by dns_rnk, score
 order by dns_rnk;
 
+---------+-------+-------------+
| dns_rnk | score | string_agg  |
+---------+-------+-------------+
|       1 |    90 | 學生A       |
|       2 |    88 | 學生B,學生C |
|       3 |    85 | 學生D       |
+---------+-------+-------------+
(3 rows)

回應上面一些討論,MySQL 取排名,用 limit 3的方式,或是Oracle用 rownum.
若是分數有相同,這樣會有疑慮,有另一種排名方式,叫 dense rank.
在 8版以後有提供此函數,請參考: https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html

之前的版本,請參考敝人以前的 https://ithelp.ithome.com.tw/articles/10136053

有提供一種方法.

/images/emoticon/emoticon33.gif

Ks iT邦新手 4 級 ‧ 2019-05-08 17:10:48 檢舉

感謝大大的答案,又學到一個新的SQL function了,
好像也只能多看多用才有辦法記得
/images/emoticon/emoticon12.gif

2
Anthony_Yang
iT邦新手 5 級 ‧ 2019-05-08 13:26:43

MSSQL

CREATE TABLE #Student
(
 class nvarchar(20),
 course nvarchar(20),
 studentname nvarchar(20),
 score int
)
insert into #Student select class = '甲班',course = '國文' , studentname = '學生A' , score = 90
insert into #Student select class = '甲班',course = '國文' , studentname = '學生B' , score = 88
insert into #Student select class = '甲班',course = '國文' , studentname = '學生C' , score = 88
insert into #Student select class = '甲班',course = '國文' , studentname = '學生D' , score = 85

SELECT DISTINCT ROW_NUMBER() OVER(order by score desc) as 排名,
  score,
  studentname=
  (
   SELECT cast(studentname AS NVARCHAR ) + ' ' 
   from #Student 
   where score=t0.score --把name一樣的加起來
   FOR XML PATH('')
  )
  from #Student t0
  group by score

[SQL]將多筆資料合併為一筆顯示(FOR XML PATH)
https://dotblogs.com.tw/kevinya/2012/06/01/72553

抱歉 這好像未將前三名考量進去

Ks iT邦新手 4 級 ‧ 2019-05-08 17:11:32 檢舉

非常感謝,我會參考看看/images/emoticon/emoticon07.gif

1
rogeryao
iT邦高手 7 級 ‧ 2019-05-08 16:48:11

MSSQL:

select top 3 ROW_NUMBER() OVER(ORDER BY score desc) as Number,
score,
left(studentnameNew,len(studentnameNew)-1) as studentnameComb
from (
SELECT distinct score,(SELECT cast(studentname AS NVARCHAR ) + ',' from Student 
where score = stu.score
FOR XML PATH('')) as studentnameNew
from Student stu
where 1=1
) as MM

https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=123b6c62282cf294f08c25dced34fc90

Ks iT邦新手 4 級 ‧ 2019-05-08 17:13:47 檢舉

非常感謝,我會參考看看/images/emoticon/emoticon05.gif

2
Sponge
iT邦新手 5 級 ‧ 2019-05-09 23:11:22

大大你好,最近工作較忙所以晚看到了這題
而問題也早已經被優秀的邦友們解決了
但看著這問題我想了下,想說能否不用特殊函數(FOR XML PATH...等)解決這問題
因此花了點時間撰寫,後來採用迴圈的方式實現,這當然不是最佳實踐方案,但應該是通用性最佳的方案吧....XD
附上完整資料表結構
測試環境為 Microsoft SQL Server 2016 Standard edition

/*
	創建測試環境
    測試環境為 Microsoft SQL Server 2016 Standard edition
*/
--測試用資料庫
USE [TestDB]
GO

--創建測試資料表
--Class
IF  EXISTS (SELECT * FROM sys.tables WHERE object_id = OBJECT_ID(N'[dbo].[class]'))
DROP TABLE [dbo].[class]
GO
CREATE TABLE [dbo].[class]
(
	[Id] INT  IDENTITY(1,1),--流水號
	[ClasseNo] NVARCHAR(5) NOT NULL,--班級代號
	[ClassName] NVARCHAR(20) NOT NULL,--班級名稱	
)
GO
--寫入測試資料
INSERT INTO [dbo].[class] VALUES (N'C01', N'比奇堡駕訓班')

--Course
IF  EXISTS (SELECT * FROM sys.tables WHERE object_id = OBJECT_ID(N'[dbo].[course]'))
DROP TABLE [dbo].[course]
GO
CREATE TABLE [dbo].[course]
(
	[Id] INT  IDENTITY(1,1),--流水號
	[CourseNo] NVARCHAR(20) NOT NULL,--課程代號
	[CourseName] NVARCHAR(20) NOT NULL,--課程名稱
	[StudentNo] NVARCHAR(10) NOT NULL--學號
)
GO
--寫入測試資料
INSERT INTO [dbo].[course] VALUES (N'P01', N'泡芙駕訓課', N'A001')
INSERT INTO [dbo].[course] VALUES (N'P01', N'泡芙駕訓課', N'A002')
INSERT INTO [dbo].[course] VALUES (N'P01', N'泡芙駕訓課', N'A003')
INSERT INTO [dbo].[course] VALUES (N'P01', N'泡芙駕訓課', N'A004')

--Studentname
IF  EXISTS (SELECT * FROM sys.tables WHERE object_id = OBJECT_ID(N'[dbo].[Studentname]'))
DROP TABLE [dbo].[Studentname]
GO
CREATE TABLE [dbo].[Studentname]
(
	[Id] INT IDENTITY(1,1),--流水號
	[StudentNo] NVARCHAR(10) NOT NULL,--學號
	[StudentName] NVARCHAR(20) NOT NULL,--學生姓名
	[ClasseNo] NVARCHAR(5) NOT NULL,--班級代號
)
GO
--寫入測試資料
INSERT INTO [dbo].[Studentname] VALUES (N'A001', N'海綿寶寶', N'C01')
INSERT INTO [dbo].[Studentname] VALUES (N'A002', N'派大星', N'C01')
INSERT INTO [dbo].[Studentname] VALUES (N'A003', N'章魚哥', N'C01')
INSERT INTO [dbo].[Studentname] VALUES (N'A004', N'珊迪', N'C01')

--Score
IF  EXISTS (SELECT * FROM sys.tables WHERE object_id = OBJECT_ID(N'[dbo].[Score]'))
DROP TABLE [dbo].[Score]
GO
CREATE TABLE [dbo].[Score]
(
	[Id] INT  IDENTITY(1,1),--流水號
	[StudentNo] NVARCHAR(10) NOT NULL,--學號
	[Score] INT NOT NULL,--分數
)
GO
--寫入測試資料
INSERT INTO [dbo].[Score] VALUES (N'A001', 90)--海綿寶寶
INSERT INTO [dbo].[Score] VALUES (N'A002', 88)--派大星
INSERT INTO [dbo].[Score] VALUES (N'A003', 88)--章魚哥
INSERT INTO [dbo].[Score] VALUES (N'A004', 85)--珊迪

/*
	題目:
		Student Table column
		class 班級
		course 課程
		studentname 學生名字
		score 分數
		找出前三位最大分數,並且同樣分數的學生顯示在同一列

	原始資料:
		海綿寶寶  90
		派大星    88
		章魚哥    88
		珊迪      85

	所期望輸出:
		第一 90 海綿寶寶
		第二 88 派大星、章魚哥
		第三 85 珊迪

	目標:不用任何特殊函數完成任務

*/
--資料觀察
SELECT * FROM [dbo].[class]
SELECT * FROM [dbo].[course]
SELECT * FROM [dbo].[Studentname]
SELECT * FROM [dbo].[score]

--開始實作
DECLARE @i int = 1--以1為開始對應流水號第1位的同學
DECLARE @iMax int = (SELECT COUNT(*) FROM [dbo].[Studentname])--取得學生總數
DECLARE @str NVARCHAR(MAX) = N''--準備存取學生姓名
DECLARE @Score INT = 0--分數

--創建答案輸出表
CREATE TABLE #Ans
(
	[Score] INT NOT NULL,--分數
	[Student] NVARCHAR(MAX)--姓名
)

--將最高的三個分數取出來
INSERT INTO #Ans
SELECT DISTINCT TOP 3 [Score] AS [Score] ,--找出最高的三個分數
			N'' AS [Student]--先準備好姓名欄位
FROM [dbo].[Score]--從分數表取得
ORDER BY [Score] DESC--降冪排列

--透過迴圈寫資料
WHILE @i <= @iMax
BEGIN	
		SELECT @str = std.[StudentName],--將變數指定為姓名
				@Score = sc.[Score]--將變數指定為分數
		FROM [dbo].[Studentname] AS std
		LEFT JOIN [dbo].[Score] AS sc
			ON std.StudentNo = sc.StudentNo
		WHERE std.[Id] = @i--指定流水號		

		--如果分數是符合最高的三個分數,就將名子更新進去
		IF @Score IN (SELECT TOP 3 [Score] FROM #Ans ORDER BY [Score] DESC)
		BEGIN
			UPDATE #Ans
			SET [Student] = ([Student] + N', ' + @str)		
			FROM #Ans
			WHERE [Score] = @Score
		END
	SET @i = @i + 1
END
SELECT [Score], 
	SUBSTRING([Student],2 ,LEN([Student])) AS [Student]--去除最前面的逗號
FROM #Ans
DROP TABLE #ANS

答案輸出:
答案輸出

我要發表回答

立即登入回答