我有以下功能,它会从Client
表中返回下一个可用客户端ID :
CREATE OR REPLACE FUNCTION getNextClientID RETURN INT AS
ctr INT;
BEGIN
SELECT MAX(NUM) INTO ctr FROM Client;
IF SQL%NOTFOUND THEN
RETURN 1;
ELSIF SQL%FOUND THEN
-- RETURN SQL%ROWCOUNT;
RAISE_APPLICATION_ERROR(-20010, 'ROWS FOUND!');
-- RETURN ctr + 1;
END IF;
END;
但是在调用此函数时,
BEGIN
DBMS_OUTPUT.PUT_LINE(getNextClientID());
END;
我得到以下结果:
我发现有点奇怪,因为Client
表不包含数据:
另外,如果我发表评论RAISE_APPLICATION_ERROR(-20010, 'ROWS FOUND!');
&将SQL%ROWCOUNT
的值记录到控制台,结果我得到 1 。
另一方面,改变时
SELECT MAX(NUM) INTO ctr FROM Client;
到
SELECT NUM INTO ctr FROM Client;
执行按预期进行。 这种行为背后的原因是什么?
答案 0 :(得分:3)
Aggregate functions将始终返回结果:
除COUNT(*),GROUPING和GROUPING_ID之外的所有聚合函数 忽略空值。您可以在参数中使用NVL函数 用于将值替换为null的聚合函数。 COUNT和 REGR_COUNT永远不会返回null,但返回数字或零。对于 所有剩余的聚合函数,如果数据集不包含 行,或仅包含具有空值的行作为聚合的参数 函数,然后函数返回null。
您可以将查询更改为:
SELECT COALESCE(MAX(num), 1) INTO ctr FROM Client;
并完全删除条件。如果不使用SELECT FOR UPDATE
,请注意并发问题。
答案 1 :(得分:2)
使用任何聚合函数进行查询且没有GROUP BY
子句总是返回1行。如果您希望空表上出现no_data_found
例外,请添加GROUP BY
子句或删除max
:
SQL> create table t (id number, client_id number);
Table created.
SQL> select nvl(max(id), 0) from t;
NVL(MAX(ID),0)
--------------
0
SQL> select nvl(max(id), 0) from t group by client_id;
no rows selected
通常使用像您这样的查询(使用max
而不使用group by
)来避免no_data_found
。
答案 2 :(得分:1)
像MAX这样的Agregate函数将始终返回一行。如果没有找到行,它将返回一行为空值。
顺便说一下,SELECT NUM INTO ctr FROM Client;
会引发一个异常,表中有多行。
您应该检查ctr
是否为空。
答案 3 :(得分:1)
其他人已经解释了为什么你的代码没有“工作”的原因,所以我不打算这样做。
您似乎是在自己创建一些描述的标识列,可能是为了支持代理键。 自行执行此操作非常危险,可能会导致应用程序出现大问题。
您不需要自己实施标识列。从Oracle 12c开始,Oracle对identity columns提供原生支持,这些支持使用sequences实现,这些版本在12c和之前的版本中可用。
序列是一个数据库对象,无论请求值的并发会话数是多少,都可以保证在调用时提供新的唯一数字。当多个会话使用时,您当前的方法极易受到冲突的影响。想象一下,2个会话同时找到表中的最大值;然后他们都为此值添加一个并尝试将此新值写回。只有一个是正确的。
请参阅How to create id with AUTO_INCREMENT on Oracle?
基本上,如果您使用序列,那么您就不需要任何此代码。
作为次要说明,您在顶部的陈述不正确:
我有以下函数,它从Client表
返回下一个可用的客户端ID
您的函数返回最大ID + 1.如果ID中存在间隙,即1,2,3,5,那么"缺少"号码(在这种情况下为4)将不予退还。由于各种原因(例如删除行)可能会出现间隙,并且不会以任何方式对您的数据库产生负面影响 - 不用担心它们。