Oracle 11 PL SQL 使用 Execute Immediate 为变量赋值
Posted
技术标签:
【中文标题】Oracle 11 PL SQL 使用 Execute Immediate 为变量赋值【英文标题】:Oracle 11 PL SQL Use Execute Immediate to assign value to variable 【发布时间】:2017-12-11 04:57:56 【问题描述】:我是 PL SQL 的新手,还在学习,但我需要解决一个问题,我对 PL SQL 的了解还不够,无法快速解决我的问题。
我引用了两个表:用户和属性。我有一个需要 3 个参数的过程:attrib_id、uid、attrib_value。
我首先用 attrib_id 查询了 attributes 表以返回属性名称并将其分配给变量。我的代码到此为止。
接下来我想在另一个select语句中使用前一个select语句创建的变量来查询users表,并返回与该变量所代表的属性关联的当前值。
代码:
PROCEDURE value_update_proc_z(attrib_id INTEGER, uid IN VARCHAR2, attrib_value IN VARCHAR2)
IS
v_old_attrib_name attributes.attribute_name%TYPE;
v_oldattrib_value varchar2(100);
v_mymsg varchar2(2000);
BEGIN
EXECUTE IMMEDIATE 'SELECT attrib_name FROM attributes WHERE indx = ''' || attrib_id || '''' INTO v_old_attrib_name;
EXECUTE IMMEDIATE 'SELECT' || v_old_attrib_name || 'FROM USERS WHERE USERID = ''' || uid || '''' INTO v_oldattrib_value;
v_mymsg := v_old_attrib_name || ' ' || v_oldattrib_value;
END value_update_proc_z;
第一个查询应该根据传递给过程的数字从属性表中返回一个值。例如,如果 attrib_id = 1,则查询将返回 first_name,如果 attrib_id = 2,则返回 last_name,如果 attrib_id = 3,将被退回的电子邮件。返回的值将分配给变量 v_old_attrib_name。
在我的 select 语句中使用变量 v_old_attrib_name,我希望第二个查询会返回一个值,例如; last_name 会返回 williams,或者 email 会返回 bob@somewhere.com。此查询的结果将分配给变量 v_oldattrib_value。
目前,第一个 Execute Immediate 有效,当我显示消息时,我可以看到该变量的值,但是当我添加第二个 Execute Immediate 时,我收到一条消息,指出操作无法完成。这不是系统产生的错误,是以前的开发者设置的消息。
我愿意接受改进建议。
谢谢!
【问题讨论】:
我会质疑你为什么需要这个程序。在调用过程中使用这样的静态 SQL 会更简单:select columnname from users where userid = :userid
我需要先获取列名,以便在第二个 select 语句中使用它。正在向该过程传递一个属性 ID 号,该 ID 号引用属性表中的属性名称。因此,如果该过程的属性 id 为 1,则该属性的名称为 first_name,即 users 表中的列名。所以,在调用过程之前我不知道。
看看调用代码。那就是设置属性“id”的地方。那是您可以更改它的地方,因此您无需在每次有人想要查询记录时都查找数据字典。您在这里所拥有的具有“EAV”模式的许多缺点(被认为是反模式)。
【参考方案1】:
动态 SQL 很难,因为它将编译错误转化为运行时错误。令人惊讶的是,这里使用动态 SQL 发布的问题有多少是简单的拼写错误,如果将语句编写为静态 SQL,则很容易发现这些问题。这里似乎是这样。
这里的标准建议是先以静态形式编写 SQL,这样您就知道代码有效。只有然后将其转换为模板 SQL 以进行动态执行,请注意空格、名称等。您的第二条语句在连接的v_old_attrib_name
变量两侧的模板 SQL 中缺少空格。
另外,如果静态 SQL 有效,请不要使用动态 SQL。例如,您的第一个语句可以是 - 并且应该是 - 静态的。
PROCEDURE value_update_proc_z(
attrib_id INTEGER,
uid IN VARCHAR2,
attrib_value IN VARCHAR2)
IS
v_old_attrib_name attributes.attribute_name%TYPE;
v_oldattrib_value varchar2(100);
v_mymsg varchar2(2000);
BEGIN
SELECT attrib_name
INTO v_old_attrib_name
FROM attributes
WHERE indx = attrib_id ;
EXECUTE IMMEDIATE
'SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID = :1'
INTO v_oldattrib_value
using uid;
v_mymsg := v_old_attrib_name || ' ' || v_oldattrib_value;
dbms_output.put_line(v_mymsg);
END value_update_proc_z;
刚刚在您的问题中注意到了这一行。
“这不是系统产生的错误,是以前的开发者设置的消息。”
天哪,但看起来这位以前的开发人员是糟糕代码的王子。他们不仅用可怕的EAV implementation 让您感到困扰,而且他们也在压制错误消息。诸如操作无法完成之类的通用消息对任何人都没有好处,尤其是对我们所有人而言。您需要知道实际的 PL/SQL 错误消息,这样才能知道程序失败的原因,这是解决问题的关键。
现在,一个设计良好的系统将具有某种形式的日志记录,可以显示和/或存储真正的 SQLERRM。您似乎没有在设计一个精心设计的系统上工作,但是您是否有任何错误日志记录?
【讨论】:
我将第一个 select 语句更改为 SQL select 并且它可以工作。在 Execute Immediate 中添加第二条语句会导致相同的错误。仅供参考,为了简化我的问题,我省略了程序中的其他代码。其他代码很好,多年来一直正常工作。我得出结论,我的代码仍然不正确。顺便说一句,我正在使用 htp.p('' || v_mymsg || ''); 在浏览器中查看结果我相信我的立即执行会阻止程序的其余部分执行。 问题来了:我们发布的代码应该可以工作。你说它不适合你。但是你正在做一些不同的事情。您还没有发布您正在运行的所有代码。 我们无法调试我们看不到的东西。您需要发布导致问题的代码。或者你可以自己想办法。选择权在你。 出于某种原因,您对答案的这种轻微修改解决了所有问题。立即执行('SELECT' || v_old_attrib_name || ' FROM USERS WHERE USERID = :1' ) INTO v_oldattrib_value using uid;我必须添加左括号和右括号。 嗯,坦率地说,这听起来像伏都教,但你得到了它的工作,这是主要的事情。【参考方案2】:在第二条语句中
EXECUTE IMMEDIATE 'SELECT' || v_old_attrib_name || 'FROM USERS WHERE USERID
...
在变量v_old_attrib_name
之前和之后应该至少存在一个space
,如下所示:
EXECUTE IMMEDIATE 'SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID
...
【讨论】:
这似乎没有帮助。我得到了和以前一样的错误。【参考方案3】:请在下面找到工作示例。不明白的地方告诉我...
问题 1:我正在获取 attrib_value,但使用 v_old_attrib_name attributes.attribute_name%TYPE;
[Attribute_Name] 声明了变量。应该是v_old_attrib_name attributes.attrib_name%TYPE;
问题 2:如上所述,在另一个答案中,您在查询中遗漏了一些空格。
问题 3:您没有 out_variable,IDK 您查看返回值的计划是什么。
create table attributes (attrib_name varchar2(20), indx number(2));
insert into attributes values('LAST_NAME',1)
create table USERS (userid number(2), last_Name varchar2(100))
insert into users values (12,'Williams')
程序:
create or replace PROCEDURE value_update_proc_z(attrib_id INTEGER, uid IN VARCHAR2, attrib_value OUT VARCHAR2)
IS
v_old_attrib_name attributes.attrib_name%TYPE;
v_oldattrib_value varchar2(100);
BEGIN
EXECUTE IMMEDIATE 'SELECT attrib_name FROM attributes WHERE indx = ''' || attrib_id || '''' INTO v_old_attrib_name;
EXECUTE IMMEDIATE 'SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID = ''' || uid || '''' INTO v_oldattrib_value;
attrib_value := v_old_attrib_name || ' ' || v_oldattrib_value;
END value_update_proc_z;
执行:请注意,我们正在打印输出变量
DECLARE
ATTRIB_ID NUMBER;
"UID" VARCHAR2(32767);
ATTRIB_VALUE VARCHAR2(32767);
BEGIN
ATTRIB_ID := 1;
"UID" := 12;
ATTRIB_VALUE := NULL;
VALUE_UPDATE_PROC_Z ( ATTRIB_ID, "UID", ATTRIB_VALUE );
DBMS_OUTPUT.Put_Line('ATTRIB_VALUE = ' || ATTRIB_VALUE);
DBMS_OUTPUT.Put_Line('');
COMMIT;
END;
【讨论】:
【参考方案4】:我根据您在架构中的要求创建了示例表。我发现您必须在代码中进行 3 处更改才能正常工作。
1.) v_old_attrib_name attributes.attribute_name%TYPE 更改为 v_old_attrib_name attributes.attrib_name%TYPE(根据开始块中的代码)。
我之所以说上述更改是因为在开始块中,当您在属性表上编写选择查询时,选择子句中的列是“attrib_name”而不是“attribute_name”。
2.) 当您使用动态 SQL 时,您不能直接用变量替换列或表名,因为 oracle 需要它们在运行时执行查询。为避免这种情况,您必须添加另一个变量,您将在其中形成动态查询,然后在立即执行中使用它。您将在下面的代码中看到这一点。
3.) 编写动态 SQL 后,请确保提供适当的空格,以免相互连接。这也将在我下面的代码中看到。
代码如下:
PROCEDURE value_update_proc_z(attrib_id INTEGER, uid IN VARCHAR2, attrib_value IN VARCHAR2)
is
v_old_attrib_name attributes.attrib_name%TYPE;
v_oldattrib_value varchar2(100);
v_mymsg varchar2(2000);
v_sql varchar2(4000);
BEGIN
EXECUTE IMMEDIATE 'SELECT attrib_name FROM attributes WHERE indx = ''' || attrib_id || '''' INTO v_old_attrib_name;
v_sql:='SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID = ''' || uid || '''' ;
EXECUTE IMMEDIATE v_sql INTO v_oldattrib_value;
v_mymsg := v_old_attrib_name || ' ' || v_oldattrib_value;
END value_update_proc_z;
希望这能解决您的问题。
谢谢 Ankit。
【讨论】:
以上是关于Oracle 11 PL SQL 使用 Execute Immediate 为变量赋值的主要内容,如果未能解决你的问题,请参考以下文章
Oracle 11 PL/SQL:检查变量是不是为空、空字符串和无结果
Oracle 11 PL SQL 使用 Execute Immediate 为变量赋值