我有一个简单的场景,我们尝试将一行插入表中并将创建的 ID 返回给客户端。我认为这将是一个简单的解决方案(在 11g 中创建的表,因此没有标识列,将其更改为标识列不是一个选项):
DECLARE
new_ID INT;
BEGIN
SELECT MAX(MY_TABLE_ID) + 1
INTO new_ID
FROM MY_SCHEMA.MY_TABLE
;
INSERT INTO MY_SCHEMA.MY_TABLE (MY_TABLE_ID)
VALUES (new_ID)
;
SELECT new_ID
FROM dual
;
COMMIT;
END;
但这会返回错误“PLS-00428:此 SELECT 语句中需要 INTO 子句”。事实证明,Oracle 不允许像 SQL Server 那样将变量用作 SELECT 子句中的表达式。
那么我们如何将这些数据返回给调用客户端呢? Every single answer关于我所看到的主题建议使用DBMS_OUTPUT来返回这个(就像这些问题中的提问者一样,我从SQL Server背景中来到这里感到困惑)。但 DMBS_OUTPUT 是一个调试工具,不适合生产代码(并非每个客户端都是 IDE!)。好的,我使用 ODP.NET 作为客户端,虽然 可以检索 PUT_LINE() 输出,但我确信开发人员会理解为什么这是一个需要避免的坏主意。同样,我不太习惯使用 RETURN 语句,在我看来,该语句应该只返回非 0 值作为错误状态。
唯一合理的方法是使用 SQL Server 的 OUTPUT.INSERTED 的 Oracle 等效项(由于显而易见的原因,@@ROWID 的等效项不是一个选项),但事实证明 RETURNING 子句还需要... INTO 子句,意味着我们再次使用变量。
在这种情况下,最巧妙的解决方案似乎是(我假设事务足够隔离以避免选择不同的执行 ID):
INSERT INTO MY_SCHEMA.MY_TABLE (MY_TABLE_ID)
SELECT MAX(MY_TABLE_ID) + 1
FROM MY_SCHEMA.MY_TABLE
;
SELECT AS NewId
FROM MY_SCHEMA.MY_TABLE
;
COMMIT;
它必须做,但无论是在性能还是编码方面,必须两次击中桌子似乎很笨拙。但最终我的问题是:有没有什么方法可以将变量的值返回给客户端,而不必先将其插入表中,或者使用不适合这种用途的方法?
一种选择是创建一个带有 OUT 参数的过程,该参数将该值返回给调用者。这是一个例子:
SQL> CREATE TABLE test
2 (
3 id NUMBER,
4 name VARCHAR2 (20)
5 );
Table created.
SQL> SET SERVEROUTPUT ON
查询使用
NVL
函数;如果没有它,如果表为空,新的ID
将是NULL
。
SQL> CREATE OR REPLACE PROCEDURE p_new_id (o_new_id OUT test.id%TYPE)
2 IS
3 BEGIN
4 SELECT NVL (MAX (id), 0) + 1 INTO o_new_id FROM test;
5
6 INSERT INTO test (id, name)
7 VALUES (o_new_id, 'Test');
8
9 COMMIT;
10 END;
11 /
Procedure created.
测试:因为这(仍然是)PL/SQL,所以我使用匿名块,该块接受该值到本地变量中并将其显示在屏幕上。你可能会做一些不同的事情。
SQL> DECLARE
2 l_new_id test.id%TYPE;
3 BEGIN
4 p_new_id (l_new_id);
5
6 DBMS_OUTPUT.put_line ('Newly inserted ID = ' || l_new_id);
7 END;
8 /
Newly inserted ID = 1
PL/SQL procedure successfully completed.
SQL> /
Newly inserted ID = 2
PL/SQL procedure successfully completed.
SQL> SELECT * FROM test;
ID NAME
---------- --------------------
1 Test
2 Test
SQL>
另一方面:请注意,这是显示您所要求的示例:如何将值返回给客户端。
如果您确实打算使用这种方法来查找(好吧,插入)下一个
ID
值,那么这很可能是错误的。它在单用户环境中工作正常,但在多用户环境中会产生重复项(或者引发错误,如果 ID 是唯一的),因为迟早两个(或更多)用户会尝试在同一位置执行相同的操作。同一时间。
也许您宁愿让数据库通过标识列(Oracle 12c 及以上)或较低数据库版本的序列+触发器来处理该问题。
此外,在 Oracle 中,我们通常让调用者决定何时提交(即我会将其从过程中删除)。
要插入一行并取回生成的标识值,请使用序列并简单地使用返回子句插入。
create sequence mysequence; -- one time only
如果使用 SQL Plus:
var pkcol integer
begin
insert into mytable (pkcol,col2,col3) values (mysequence.nextval,'abcd','efgh')
returning pkcol into :pkcol;
end;
/
print pkcol -- now it's in the sqlplus client and you can do something with it, which in sqlplus is not much other than printing it
取回值的关键是使用正确的绑定。这就是使用 SQL Plus 客户端执行此操作的方式。如果使用不同的客户端(您提到 ODP.NET),请使用它提供的约定将
:pkcol
绑定到该环境中的客户端变量。在这种情况下,您不会使用 var pkcol integer
或 print pkcol
命令,但 PL/SQL 片段是相同的。如何向数据库提交块并将返回值绑定到客户端变量取决于所讨论的客户端技术。
另一种选择是根本不要求任何回报。首先生成序列,然后进行插入:
select mysequence.nextval from dual
-- 获取客户端变量 pkcol
然后,
insert into mytable (pkcol,col2,col3) values (:pkcol,'abcd','efgh')
with
:pkcol
定义为绑定到 pkcol
局部变量。插入时无需返回,因为您已经知道 pkcol
值。