Oracle SQL:嵌套子查询,外部相同,内部不同

Oracle SQL:嵌套子查询,外部相同,内部不同,sql,oracle,subquery,union,Sql,Oracle,Subquery,Union,在这种情况下,我有几个子查询,由UNION连接,每个子查询嵌套一个内部子查询。外部子查询彼此完全相同,而内部查询不同且唯一 对整个外部子查询的重用对于读取和进行更改来说是很麻烦的,如果可以一次性定义并重用它们,这将是非常有益的。因此,这是一个关于创建可重用SQL查询的问题,但是要使用作为参数传递的不同内部子查询 对于我的示例,我将提供一个简化的案例,它与我的实际代码有相同的问题 我们在项目中使用Oracle SQL 假设我们有一个学校或大学的数据库,表中有PERSON、STUDENT、GRADE

在这种情况下,我有几个子查询,由UNION连接,每个子查询嵌套一个内部子查询。外部子查询彼此完全相同,而内部查询不同且唯一

对整个外部子查询的重用对于读取和进行更改来说是很麻烦的,如果可以一次性定义并重用它们,这将是非常有益的。因此,这是一个关于创建可重用SQL查询的问题,但是要使用作为参数传递的不同内部子查询

对于我的示例,我将提供一个简化的案例,它与我的实际代码有相同的问题

我们在项目中使用Oracle SQL

假设我们有一个学校或大学的数据库,表中有PERSON、STUDENT、GRADE和COURSE,所有这些都是通过FK关系连接起来的

我需要运行一个收集列表的查询,对每个条件计算一次人数:

  • 姓氏以字母“E”开头的学生人数
  • 20岁以上学生人数
  • 女性人数(包括但不限于学生)
  • 以B级或更高成绩通过“中级挪威语”课程的学生人数
预期结果:

  | Description                                     | Number_of_students
1 | Last names beginning with letter "E"            |  32
2 | Older than 20 years                             | 154
3 | All female persons                              | 356 
4 | Passed "Intermediate Norwegian" with grade >= B |  12
下面是一个应该满足我需要的查询。 它由多个子查询组成,这些子查询通过UNION连接,并且都有自己独特的内部查询

代码远非辉煌,但这与重点无关。真正的问题是大幅提高可读性。外部子查询具有相同的可重用结构,但内部子查询不同

SELECT * FROM
    -- 1st entry: Number of students on the last name 'E'
    (SELECT 'Last names beginning with letter "E"' AS Description, count(*) AS Number_of_students FROM 
      FROM PERSON p, STUDENT s, GRADE g, COURSE c
        WHERE p.ID = s.PERSON_ID 
        AND s.ID = g.STUDENT_ID 
        AND g.COURSE_ID = c.ID
        -- ... other complex code here

        AND p.ID IN(
          SELECT DISTINCT p1.ID FROM PERSON p1, STUDENT s1 
              WHERE p1.ID = s1.PERSON_ID AND p1.LASTNAME LIKE 'E%'
        )
    )
UNION

-- 2nd entry: Number of students older than 20 years
    (SELECT 'Older than 20 years' AS Description, count(*) AS Number_of_students FROM 
      FROM PERSON p, STUDENT s, GRADE g, COURSE c
        WHERE p.ID = s.PERSON_ID 
        AND s.ID = g.STUDENT_ID 
        AND g.COURSE_ID = c.ID
        -- ... other complex code here

        AND p.ID IN(
          SELECT DISTINCT p2.ID FROM PERSON p2, STUDENT s2 
              WHERE p2.ID = s2.PERSON_ID AND p2.AGE > 20
        )
    )
UNION

-- 3rd entry: Number of female persons, including but not limited to students
    (SELECT 'All female persons' AS Description, count(*) AS Number_of_students FROM 
      FROM PERSON p, STUDENT s, GRADE g, COURSE c
        WHERE p.ID = s.PERSON_ID 
        AND s.ID = g.STUDENT_ID 
        AND g.COURSE_ID = c.ID
        -- ... other complex code here

        AND p.ID IN(
          SELECT DISTINCT p3.ID FROM PERSON p3 WHERE p3.GENDER = 'Female'
        )
    )
UNION

-- 4th entry: Students who passed the course "Intermediate Norwegian" with grade B or higher
    (SELECT 'Passed "Intermediate Norwegian" with grade >= B' AS Description, count(*) AS Number_of_students FROM 
      FROM PERSON p, STUDENT s, GRADE g, COURSE c
        WHERE p.ID = s.PERSON_ID 
        AND s.ID = g.STUDENT_ID 
        AND g.COURSE_ID = c.ID
        -- ... other complex code here

        AND p.ID IN(
          SELECT DISTINCT p4.ID FROM PERSON p4, STUDENT s4, GRADE g4 AND COURSE c4
            WHERE p4.ID = s4.PERSON_ID 
            AND s4.ID = g4.STUDENT_ID 
            AND g4.COURSE_ID = c4.ID
            AND (g4.GRADE = 'A' OR g4.GRADE = 'B')
            AND c4.COURSE_NAME = 'Intermediate Norwegian'
        )
    )
就像我说的,代码一点也不精彩。如果你们中的一些人对刚读到的东西感到畏缩,我不会感到惊讶

例如,整个第四个查询可以很容易地替换为一个查询,其中您将整个内部查询替换为g.GRADE='a'或'B'和c.COURSE_NAME='Intermediate norgian'

但正如我所说,这不是重点。 每个外部子查询都具有相同的结构:

 (SELECT 'Passed "Intermediate Norwegian" with grade >= B' AS Description, count(*) AS Number_of_students FROM 
      FROM PERSON p, STUDENT s, GRADE g, COURSE c
        WHERE p.ID = s.PERSON_ID 
        AND s.ID = g.STUDENT_ID 
        AND g.COURSE_ID = c.ID
        -- ... other complex code here

        AND p.ID IN(
           -- Inner Sub-query here
        )
而每个子查询都有一个彼此不同的内部查询。像第一个和第三个:

SELECT DISTINCT p1.ID FROM PERSON p1, STUDENT s1 WHERE p1.ID = s1.PERSON_ID AND p1.LASTNAME LIKE 'E%'

我需要什么:

  | Description                                     | Number_of_students
1 | Last names beginning with letter "E"            |  32
2 | Older than 20 years                             | 154
3 | All female persons                              | 356 
4 | Passed "Intermediate Norwegian" with grade >= B |  12
我正在使用的实际代码要复杂得多,但与上面的示例中所示的问题相同

  • 结果必须是一个包含多个数字的列表,每个数字按照各自不同的标准进行分类(最好在第一列中描述)
  • 它由几个子查询组成,由UNION连接
  • 这些子查询中的每一个子查询都是完全相同的,但内部子查询是完全唯一的,并且与其他子查询不同
  • 由此产生的代码是一个巨大的野兽,但理论上,如果外部代码只编写了一次,并与作为参数传递的不同内部代码一起重用,则可以使其更具可读性
我最近在Oracle SQL中遇到了WITH子句。 类似于以下更改的内容将非常有益:

WITH outer_sub_query AS (
   SELECT 'DESCRIPTION HERE' AS Description, count(*) AS Number_of_students FROM 
      FROM PERSON p, STUDENT s, GRADE g, COURSE c
        WHERE p.ID = s.PERSON_ID 
        AND s.ID = g.STUDENT_ID 
        AND g.COURSE_ID = c.ID
        -- ... other complex code here

        AND p.ID IN(
           -- INSERT INNER SUB-QUERY HERE
        )
)
SELECT * FROM (
    outer_sub_query -- Last Names beginning with letter 'E'
    UNION 
    outer_sub_query -- Age > 20
    UNION 
    outer_sub_query -- All female
    UNION 
    outer_sub_query -- Passed that course with grade >= B
)
不幸的是,我的需要还没有得到满足。我仍然需要传递内部子查询以及描述。类似于此:

SELECT * FROM (
    outer_sub_query(
        'Last names beginning with letter "E",'
        SELECT DISTINCT p1.ID FROM PERSON p1, STUDENT s1 
          WHERE p1.ID = s1.PERSON_ID AND p1.LASTNAME LIKE 'E%'
    )
    UNION 
    outer_sub_query(
        'Older than 20 years.'
        SELECT DISTINCT p2.ID FROM PERSON p2, STUDENT s2 
          WHERE p2.ID = s2.PERSON_ID AND p2.AGE > 20
    )
    UNION 
    outer_sub_query(
        'All female persons'
        SELECT DISTINCT p3.ID FROM PERSON p3 WHERE p3.GENDER = 'Female'
    )
    UNION 
    outer_sub_query(
        'Passed "Intermediate Norwegian" with grade >= B'
        SELECT DISTINCT p4.ID FROM PERSON p4, STUDENT s4, GRADE g4 AND COURSE c4
        WHERE p4.ID = s4.PERSON_ID 
        AND s4.ID = g4.STUDENT_ID 
        AND g4.COURSE_ID = c4.ID
        AND (g4.GRADE = 'A' OR g4.GRADE = 'B')
        AND c4.COURSE_NAME = 'Intermediate Norwegian'
    )
)
问题:

  | Description                                     | Number_of_students
1 | Last names beginning with letter "E"            |  32
2 | Older than 20 years                             | 154
3 | All female persons                              | 356 
4 | Passed "Intermediate Norwegian" with grade >= B |  12
现在,我们很容易想到定义函数。但它仍然给我带来了一些问题:

  • 乍一看,WITH子句似乎不接受可以传递的参数。SQL或Oracle SQL中是否有其他预先存在的子句或函数处理此问题
  • 是否可以将内部子查询从外部子查询中提取出来,并且仍然获得相同的结果?(请记住:外部子查询本身没有更改)
  • 如果我要定义一个处理这个问题的函数,是否可以像上面那样传递纯SQL代码
  • 我还缺少其他智能解决方案吗
感谢您的建议。

在您的案例中,一个通用的表表达式(如您已经建议的)似乎是减少代码重复的一种可能的方法,但您试图让它为您做的太多了。CTE不能以您希望的方式进行参数化;如果它们是,那么像您想象的那样的使用将不再有它们的共同点

是的,您可以编写一个表值函数,但这似乎有点过头了,而且查询计划员很难进行分析。以下是您可以使用CTE的范围:

WITH student_grades AS (
 SELECT
   p.id AS id,
   p.lastname AS lastname,
   p.age AS age,
   p.gender AS gender,
   c.course_name AS course_name,
   g.grade AS grade
 FROM
   -- You really, really should use ANSI JOIN syntax:
   PERSON p
   JOIN STUDENT s ON p.ID = s.PERSON_ID
   JOIN GRADE g   ON s.ID = g.STUDENT_ID 
   JOIN COURSE c  ON g.COURSE_ID = c.ID
   -- WHERE ... other complex code here
)
然后,您可以继续查询

-- 1st entry: Number of students on the last name 'E'
SELECT
  'Last names beginning with letter "E"' AS Description,
  count(distinct sg1.id) AS Number_of_students
FROM student_grades sg1
WHERE sg1.lastname LIKE 'E%'

UNION

-- 2nd entry: Number of students older than 20 years
SELECT
  'Older than 20 years' AS Description,
  count(distinct sg2.id) AS Number_of_students
FROM student_grades sg2
WHERE sg2.AGE > 20

UNION

-- 3rd entry: Number of female persons, including but not limited to students
-- NOTE: THIS ONE MATCHES YOUR ORIGINAL, WHICH IS INCORRECT
SELECT
  'All female persons' AS Description,
  count(distinct sg3.id) AS Number_of_students
FROM student_grades sg3
WHERE sg3.GENDER = 'Female'

UNION

-- 4th entry: Students who passed the course "Intermediate Norwegian" with grade B or higher
SELECT
  'Passed "Intermediate Norwegian" with grade >= B' AS Description,
  count(distinct sg4.id) AS Number_of_students
FROM student_grades sg4
WHERE
  sg4.COURSE_NAME = 'Intermediate Norwegian'
  AND sg4.grade IN ('A', 'B')
这实际上是一个显著的进步。请特别注意,您不需要将条件(无论是否子查询)传递到CTE中;相反,您可以查询CTE(也可以将其连接到其他表等)。当然,部分原因是你的“内部”子查询是一种非常可怕的处理方式;相反,我使用了
count(distinct sg.id)
,只要
person.id
为非空,就可以实现与那些子查询相同的功能,我认为这是因为它是PK

但也要注意,即使是需要一个不同的计数(以及查询的第三部分的错误),也是因为首先尝试使用相同的公共中间结果来完成所有四个部分。您不需要加入课程或成绩信息来查询严格与个人特征相关的信息,只要学生与个人的关系为0,1:1,省略课程和成绩信息将免费为您提供一个明确的计数

至于第三部分,加入
student
表将结果限制在学生身上,这是您不想要的。事实上,您没有在“内部”子查询中设置该限制是不相关的;您正在使用该子查询来筛选只包括最初是学生的人的结果。因此,*在这种情况下,您的方法无法产生您想要的结果**

也许是
select      count(case when lastname like 'e%' then 1 end)  as lastname_starts_with_e
           ,count(case when age > 20           then 1 end)  as age_greater_than_20
           ,count(case when gender = 'Female'  then 1 end)  as is_female

from        person
;