【问题标题】:How to select into a variable in PL/SQL when the result might be null?当结果可能为空时,如何在 PL/SQL 中选择变量?
【发布时间】:2011-11-02 02:26:27
【问题描述】:

有没有办法只运行一次查询以选择变量,考虑到查询可能不返回任何内容,那么在这种情况下,变量应该为空。

目前,我不能直接执行select into 变量,因为如果查询不返回任何内容,PL/SQL 会抱怨变量未设置。我只能运行两次查询,第一次进行计数,如果计数为零,则将变量设置为 null,如果计数为 1,则选择变量。

所以代码是这样的:

v_column my_table.column%TYPE;
v_counter number;
select count(column) into v_counter from my_table where ...;
if (v_counter = 0) then
    v_column := null;
elsif (v_counter = 1) then
    select column into v_column from my_table where ...;
end if;

谢谢。

更新: 我没有使用异常的原因是我在分配了v_column之后仍然有一些以下逻辑,我必须在异常部分使用goto才能跳转回下面的代码。我有点犹豫goto 行。

【问题讨论】:

    标签: select plsql


    【解决方案1】:

    COALESCE 将始终返回第一个非空结果。通过这样做,您将获得所需的计数或 0:

    select coalesce(count(column) ,0) into v_counter from my_table where ...;
    

    【讨论】:

    • Count 将始终返回一个数字,无需合并它。但这个问题实际上并不是关于计数。作者只是为了避免异常而进行计数。如果您删除聚合函数,coalesce 将无助于避免 NO_DATA_FOUND,因为可能没有行。
    • 另外,像这样将函数包裹在列周围通常会跳过任何可能已使用的索引,因为编译器无法确定函数是否会更改列值,从而使其在索引中无法找到,因此它根本不寻找任何索引。 (您可以创建一个基于函数的索引来提供帮助,但说真的,谁知道这样做呢?)
    【解决方案2】:

    从上面的所有答案中,Björn's answer 似乎是最优雅和最简短的。我个人多次使用这种方法。 MAX 或 MIN 函数同样可以很好地完成这项工作。下面是完整的 PL/SQL,只需指定 where 子句。

    declare v_column my_table.column%TYPE;
    begin
        select MIN(column) into v_column from my_table where ...;
        DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
    end;
    

    【讨论】:

      【解决方案3】:

      我知道这是一个旧线程,但我仍然认为值得回答。

      select (
              SELECT COLUMN FROM MY_TABLE WHERE ....
              ) into v_column
      from dual;
      

      使用示例:

      declare v_column VARCHAR2(100);
      begin
        select (SELECT TABLE_NAME FROM ALL_TABLES WHERE TABLE_NAME = 'DOES NOT EXIST')
        into v_column 
        from dual;
        DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
      end;
      

      【讨论】:

      • 是的,它是 PLSQL。它工作正常。为简单起见,我只是跳过了开头和结尾的内容。
      • @Miki:我添加了一个示例来说明它是 PLSQL 并且可以工作。
      • 我在这里看到了这种技术:stackoverflow.com/a/42415454/577052 当我将多列写入变量时,它似乎不起作用。我得到ORA-00913too many values.
      • 感谢您的评论!没错,它只适用于 1 列,因为标量子查询只能返回 1 个值。我希望有人可以改变这一点......
      • 我分离了这个技巧并创建了一个漂亮的选择查询。谢谢:(select round(round(months_between('30-may-2018',c.column_date),0)/12,1) from dual) as josh
      【解决方案4】:

      使用 MAX 怎么样? 这样,如果没有找到数据,则将变量设置为 NULL,否则设置为最大值。
      由于您期望值为 0 或 1,因此 MAX 应该可以使用。

      v_column my_table.column%TYPE;
      select MAX(column) into v_column from my_table where ...;
      

      【讨论】:

      • 不错,查询返回多行时也可以使用
      【解决方案5】:

      使用Cursor FOR LOOP Statement 是我最喜欢的方式。

      它比使用显式游标更安全,因为您不需要记住关闭它,因此您不能“泄漏”游标。

      您不需要“进入”变量,不需要“获取”,也不需要捕获和处理“未找到数据”异常。

      试试吧,你永远不会回去。

      v_column my_table.column%TYPE;
      
      v_column := null;
      
      FOR rMyTable IN (SELECT COLUMN FROM MY_TABLE WHERE ....) LOOP
        v_column := rMyTable.COLUMN;
        EXIT;  -- Exit the loop if you only want the first result.
      END LOOP;
      

      【讨论】:

        【解决方案6】:

        我使用这种语法是为了灵活性和速度-

            begin
            --
            with KLUJ as
            ( select 0 ROES from dual
               union 
              select count(*) from MY_TABLE where rownum = 1
            ) select max(ROES) into has_rows from KLUJ;
            --
            end;
        

        Dual 返回 1 行,rownum 添加 0 或 1 行,而 max() 组正好为 1。这为表中没有行提供 0,为任何其他行数提供 1。

        我将 where 子句扩展为按条件计算行数,删除 rownum 以计算满足条件的行数,并增加 rownum 以计算满足条件的行数达到限制。

        【讨论】:

          【解决方案7】:

          我建议使用游标。游标提取始终是单行(除非您使用批量集合),并且游标不会自动抛出 no_data_found 或 too_many_rows 异常;虽然您可以在打开后检查游标属性以确定您是否有一行以及有多少。

          declare
          v_column my_table.column%type;
          l_count pls_integer;
          cursor my_cursor is
            select count(*) from my_table where ...;
          
          begin
            open my_cursor;
              fetch my_cursor into l_count;
            close my_cursor;
          
            if l_count = 1 then
              select whse_code into v_column from my_table where ...;
            else
              v_column := null;
            end if;
          end;
          

          或者,更简单:

              declare
              v_column my_table.column%type;
              cursor my_cursor is
                select column from my_table where ...;
          
              begin
                open my_cursor;
                  fetch my_cursor into v_column;
                  -- Optional IF .. THEN based on FOUND or NOTFOUND
                  -- Not really needed if v_column is not set
                  if my_cursor%notfound then
                    v_column := null;
                  end if;
                close my_cursor;
              end;
          

          【讨论】:

          • 感谢您的回复。但是我试图避免的是运行相同where子句的查询两次,第一个是判断条件是否存在,第二个是一种是将现有记录提取到变量中。
          • @Sapience:明白了,我忽略了你的第一句话。在这种情况下,使用游标属性会变得非常简单(我建议的第二个选项)。只需在游标中编写您的实际查询(没有COUNT(*)),打开游标并将结果提取到本地变量中。然后,使用%FOUND 属性来确定所需的状态。这样您只需执行一次查询,不需要异常块并避免SELECT .. INTO 潜在异常。
          【解决方案8】:

          您可以通过将变量设置为NULL 来简单地处理NO_DATA_FOUND 异常。这样,只需要一个查询。

              v_column my_table.column%TYPE;
          
          BEGIN
          
              BEGIN
                select column into v_column from my_table where ...;
              EXCEPTION
                WHEN NO_DATA_FOUND THEN
                  v_column := NULL;
              END;
          
              ... use v_column here
          END;
          

          【讨论】:

          • @Adam,我没有使用异常的原因是我在分配v_column后仍然有一些以下逻辑,我必须在异常部分跳回下面的代码。我有点犹豫goto 行。
          • @Sapience,这不是问题,只需将逻辑放在这个 nested 块的 END 之后即可。
          • @Shannon:感谢您的编辑。我很自然地使用匿名 PL/SQL 块,所以我没有意识到它是模棱两可的。
          猜你喜欢
          • 2014-12-26
          • 1970-01-01
          • 2011-08-02
          • 1970-01-01
          • 1970-01-01
          • 2020-09-05
          • 2010-11-03
          • 2021-03-02
          • 2018-11-30
          相关资源
          最近更新 更多