【问题标题】:Oracle SQL correlated subquery to fetch exactly one rowOracle SQL 关联子查询以准确获取一行
【发布时间】:2013-04-03 09:37:18
【问题描述】:

在 Oracle SQL 相关子查询中,

我需要在子查询上有一个条件来获取一行(如果有更多行,它不应该获取任何行)。

下面的查询工作得很好。但它涉及两次查询同一张表。

SELECT m.pk_1
      ,m.pk_2
      ,m.fld_3
      ,(SELECT c.pk_1
        FROM   child_tab c
        WHERE  (SELECT COUNT(*)
                FROM   child_tab c2
                WHERE  c2.pk_1 = m.pk_1
                AND    rownum <= 2) = 1
        AND    c.pk_1 = m.pk_1) c_pk_1
FROM   master_tab m
WHERE  m.pk_1 = '018'
AND    m.pk_2 = 'value'

通过使用解析函数重新设计子查询,下面的查询给出了错误

SELECT m.pk_1
      ,m.pk_2
      ,m.fld_3
      ,(SELECT ch.pk_1
        FROM   (SELECT COUNT(*) over() cnt
                      ,c.pk_1
                FROM   child_tab c
                WHERE  c.pk_1 = m.pk_1
                AND    rownum <= 2) ch
        WHERE  cnt = 1
        AND    c.pk_1 = m.pk_1) c_pk_1
FROM   master_tab m
WHERE  m.pk_1 = '018'
AND    m.pk_2 = 'value'

ORA-00904 m.pk_1 is an invalid identifier

有没有更好的方法使用 Oracle SQL 来做到这一点?

更新

我真正的要求是我将这个 SQL 生成为一个递归过程,它会导致很多这样的级别。而且我选择的数据不会局限于key。而且我只需要 SQL 解决方案,所以 PLSQL 不是一个选择。

【问题讨论】:

    标签: sql oracle analytics correlated-subquery


    【解决方案1】:

    考虑一下。我没有任何方法来执行和检查这个。所以,如果有任何问题,请告诉我

       SELECT m.pk_1, m.pk_2, m.fld_3, C.PK1
       FROM MASTER_TAB M, CHILD_TAB C
       WHERE c.pk_1 = m.pk_1 AND m.pk_1 = '018'
       AND    m.pk_2 = 'value'
       AND EXISTS
       (
       SELECT *
       FROM CHILD_TAB C2
       c2.pk_1 = m.pk_1
       HAVING COUNT(*) = 1
       )
    

    已编辑:

       SELECT m.pk_1, m.pk_2, m.fld_3, C.PK1
       FROM MASTER_TAB M, (
       SELECT c2.pk_1
       FROM CHILD_TAB C2
       HAVING COUNT(*) = 1
       ) C
       WHERE c.pk_1 = m.pk_1 AND m.pk_1 = '018'
       AND    m.pk_2 = 'value'
    

    【讨论】:

    • 此查询再次对子表进行两次选择,但它不会像您在 SELECT * 而那样工作,而 HAVING count(*) 将选择限制为 GROUP BY 列。不使用“分组依据”将使您选择无。
    • 这也行不通。 SELECT c2.pk_1 FROM CHILD_TAB C2 HAVING COUNT(*) = 1c2.pk_1 无法选择。请阅读“通过拥有分组”
    【解决方案2】:

    Oracle 无法将m.pk_1 的值推送到下两层的子查询中。但是,鉴于您在顶级 where 子句中提供了 m.pk_1 的值,您可以直接将其放入标量子查询中,如下所示:

    SELECT m.pk_1
          ,m.pk_2
          ,m.fld_3
          ,(SELECT ch.pk_1
            FROM   (SELECT COUNT(*) over() cnt
                          ,c.pk_1
                    FROM   child_tab c
                    WHERE  c.pk_1 = '018'
                    AND    rownum <= 2) ch
            WHERE  cnt = 1
            AND    c.pk_1 = m.pk_1) c_pk_1
    FROM   master_tab m
    WHERE  m.pk_1 = '018'
    AND    m.pk_2 = 'value';
    

    或者,鉴于m.pk_1 = c.pk_1,您无需选择c.pk_1,只需使用case语句检查行是否存在,如果只有一行则返回m.pk_1

    SELECT m.pk_1
          ,m.pk_2
          ,m.fld_3
          ,CASE WHEN (SELECT COUNT(*)
                      FROM   child_tab c
                      WHERE  c.pk_1 = m.pk_1
                      AND    rownum <= 2) = 1 THEN
              m.pk_1
           END c_pk_1
    FROM   master_tab m
    WHERE  m.pk_1 = '018'
    AND    m.pk_2 = 'value';
    

    最后,您始终可以创建一个 PL/SQL 函数来为您进行查找,当有 too_many_rows 时返回 null。如果您有少量不同的 m.pk_1 值并且您使用的是 11g,则这可能是 result cache function,它应该表现良好:

    CREATE FUNCTION get_id(m_pk master_tab.pk_1%TYPE) RETURN child_tab.pk_1%TYPE AS
      retval child_tab.pk_1%TYPE;
    BEGIN
    
      SELECT t.pk_1
      INTO   retval
      FROM   child_tab t
      WHERE  t.pk_1 = m_pk
      AND    ROWNUM <= 2;
    
      RETURN retval;
    
    EXCEPTION
      WHEN TOO_MANY_ROWS THEN
        RETURN NULL;
    END;
    
    SELECT m.pk_1
          ,m.pk_2
          ,m.fld_3
          ,get_id(m.pk_1) c_pk_1
    FROM   master_tab m
    WHERE  m.pk_1 = '018'
    AND    m.pk_2 = 'value';
    

    【讨论】:

    • 感谢您的努力。我真正的要求是我将这个 SQL 生成为一个递归过程,它会导致很多这样的级别。因此,使用“硬编码 where 子句”的第一种方法是行不通的。而且我选择的数据将不限于密钥,因此第二种方法也不适用于我。而且我只需要 SQL 解决方案,所以 PLSQL 不是一个选择。
    • 但是这些解决方案非常适合我提出的问题。谢谢。
    • 我不太确定你想要达到什么目的。我认为您需要创建一个新问题来描述您需要什么来解释业务需求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多