0

我在 Oracle 12 中有一个问题,最容易用传统的数据库设计方案来解释,即学生、班级和参加称为注册的课程的学生。我很理解这个模型。我有一个场景,我需要得到一个完整的列表,所有学生反对所有课程,以及他们是否正在上课......

让我们在这里使用这个表格设计......

CREATE TABLE CLASSES 
(CLASSID  VARCHAR2(10) PRIMARY KEY,
CLASSNAME  VARCHAR2(25),
INSTRUCTOR VARCHAR2(25) );


CREATE TABLE STUDENTS
(STUDENTID  VARCHAR2(10) PRIMARY KEY,
STUDENTNAMENAME  VARCHAR2(25)
STUDY_MAJOR VARCHAR2(25) );

CREATE TABLE REGISTRATION
(
 CLASSID VARCHAR2(10 BYTE), 
 STUDENTID VARCHAR2(10 BYTE), 
 GRADE NUMBER(4,0), 
 CONSTRAINT "PK1" PRIMARY KEY ("CLASSID", "STUDENTID"),
 CONSTRAINT "FK1" FOREIGN KEY ("CLASSID") REFERENCES "CLASSES" ("CLASSID") ENABLE, 
 CONSTRAINT "FK2" FOREIGN KEY ("STUDENTID") REFERENCES "EGR_MM"."STUDENTS" ("STUDENTID") ENABLE
 ) ;

因此,假设以下... 300 名学生和 15 个不同的班级... 注册表将显示有多少学生参加了多少课程... 我需要的是该信息加上所有未采取的组合...即我需要一个显示所有可能组合的报告(SQL 语句)...即 300 x 15,然后该行是否存在于注册表中...例如,输出应如下所示...

STUDENTID   Class1_GRADE  Class2_Grade      Class3_Grade`       Class4_Grade
101         A               B                   Not Taking          A
102         C               Not Taking          Not Taking          Not Taking
****** THIS STUDENT NOT TAKING ANY CLASSES So NOT in the Registrations Table
103         Not Taking      Not Taking          Not Taking          Not Taking  

这也可以,我可能可以做一个 PIVOT 来获得上面的列表。

STUDENTID   CLASSID  GRADE
101         Class1    A
101         Class2    B
101         Class3    Not Taking
101         Class4    A
...
102         Class1    C
102         Class2    Not Taking
102         Class3    Not Taking
102         Class4    Not Taking
...
103         Class1    Not Taking  // THIS STUDENT NOT TAKING ANY CLASSES
103         Class2    Not Taking
103         Class3    Not Taking
103         Class4    Not Taking

我如何填写缺失的数据,即学生和未参加的课程的组合......?

4

2 回答 2

2

CROSS JOIN学生和班级,然后LEFT OUTER JOIN是注册,然后用于COALESCE获取Not taken值:

SELECT s.studentid,
       c.classid,
       COALESCE( TO_CHAR( r.grade ), 'Not taken' ) AS grade
FROM   students s
       CROSS JOIN classes c
       LEFT OUTER JOIN registration r
       ON ( s.studentid = r.studentid AND c.classid = r.classid )

其中,如果您有数据:

INSERT INTO Classes
SELECT LEVEL,
       'Class' || LEVEL,
       'Instructor' || LEVEL
FROM   DUAL
CONNECT BY LEVEL <= 3;

INSERT INTO Students
SELECT TO_CHAR( LEVEL, 'FM000' ),
       'Student' || LEVEL,
       'Major'
FROM   DUAL
CONNECT BY LEVEL <= 5;

INSERT INTO Registration
SELECT 1, '001', 4 FROM DUAL UNION ALL
SELECT 1, '002', 2 FROM DUAL UNION ALL
SELECT 1, '003', 5 FROM DUAL UNION ALL
SELECT 2, '001', 3 FROM DUAL UNION ALL
SELECT 3, '001', 1 FROM DUAL;

然后它输出:

学生证 | 类 | 年级    
:-------- | :-------- | :--------
001 | 1 | 4        
002 | 1 | 2        
003 | 1 | 5        
001 | 2 | 3        
001 | 3 | 1        
005 | 1 | 不采取
004 | 2 | 不采取
003 | 3 | 不采取
005 | 3 | 不采取
005 | 2 | 不采取
002 | 2 | 不采取
003 | 2 | 不采取
004 | 1 | 不采取
002 | 3 | 不采取
004 | 3 | 不采取

如果你想旋转它,那么:

SELECT *
FROM   (
  SELECT s.studentid,
         c.classid,
         COALESCE( TO_CHAR( r.grade ), 'Not taken' ) AS grade
  FROM   students s
         CROSS JOIN classes c
         LEFT OUTER JOIN registration r
         ON ( s.studentid = r.studentid AND c.classid = r.classid )
)
PIVOT ( MAX( grade ) FOR classid IN (
  1 AS Class1,
  2 AS Class2,
  3 AS Class3
) )
ORDER BY StudentID

哪个输出:

学生证 | 一级 | 2 级 | CLASS3   
:-------- | :-------- | :-------- | :--------
001 | 4 | 3 | 1        
002 | 2 | 未采取 | 不采取
003 | 5 | 未采取 | 不采取
004 | 未采取 | 未采取 | 不采取
005 | 未采取 | 未采取 | 不采取

db<>在这里摆弄

于 2020-10-01T20:24:22.463 回答
0

这只是条件聚合:

select s.studentid,
       max(case when r.classid = 1 then r.grade end) as class1_grade,
       max(case when r.classid = 2 then r.grade end) as class2_grade,
       . . .
from students s left join
     registrations r
     on r.studentid = s.studentid;

您必须明确列出列。为避免这种情况,您需要动态 SQL ( execute immediate)。

获得每行一个等级的结果更简单。使用 across join生成行并使用 aleft join引入值:

select s.studentid, c.classid, r.grade
from students s cross join
     classes c left join
     registrations r
     on r.studentid = s.studentid and r.classid = c.classid;
于 2020-10-01T18:51:53.857 回答