动态 SQL 和 ORA-00911

Posted

技术标签:

【中文标题】动态 SQL 和 ORA-00911【英文标题】:Dynamic SQL and ORA-00911 【发布时间】:2012-06-28 17:27:47 【问题描述】:

我有一个名为 Tamaris 的数据库,其中包含一个表 User。每次在我的用户表中插入一行时,我都会创建一个触发器来在数据库中创建一个新用户。这是 PL/SQL 代码:

CREATE OR REPLACE
TRIGGER UTILISATEUR_CREATE_USER_TRG 
AFTER INSERT ON UTILISATEUR 
FOR EACH ROW
DECLARE
  nom_compte NVARCHAR2(20 CHAR);
  str_create VARCHAR2(300);
  str_grant VARCHAR(250);
  type_compte NUMBER;
  unauthorized_exception EXCEPTION;
BEGIN

  CASE
    WHEN :new.idtypecompte = 1 THEN
      nom_compte := :new.pseudoutilisateur;
      type_compte := 1;
    WHEN :new.idtypecompte = 2 THEN
      nom_compte := 'AC_'|| :new.pseudoutilisateur;
      type_compte := 2;
    WHEN :new.idtypecompte = 3 THEN
      RAISE unauthorized_exception;
  END CASE;

  str_create := 'CREATE USER '|| nom_compte ||' IDENTIFIED BY '|| :new.passwordutilisateur ||' DEFAULT TABLESPACE tamaris TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON tamaris;' ;
  EXECUTE IMMEDIATE str_create;



  IF type_compte = 1 THEN
    str_grant := 'GRANT Base_User TO '|| nom_compte ||';' ;
    EXECUTE IMMEDIATE str_grant;
  ELSE
    str_grant := 'GRANT Adv_User TO '|| nom_compte ||';' ;
    EXECUTE IMMEDIATE str_grant;
  END IF;

EXCEPTION
  WHEN unauthorized_exception THEN
      dbms_output.put_line('Impossible de créer un autre gestionnaire');
END;

当我在表用户中插入一行时,触发器触发,我得到了这个:

在“TAMARIS”上保存修改时出错。“UTILISATEUR”: 第 3 行:ORA-00911:无效字符 ORA-06512:在“TAMARIS.UTILISATEUR_CREATE_USER_TRG”,第 22 行 ORA-04088: 执行“TAMARIS.UTILISATEUR_CREATE_USER_TRG”时出错 ORA-06512:在第 1 行

作为记录,str_create 中的请求在带有随机参数的触发器之外工作(仅当使用 BEGIN;END; 包装时)。因此我尝试了:

str_create := 'BEGIN CREATE USER '|| nom_compte ||' IDENTIFIED BY '|| :new.passwordutilisateur ||' DEFAULT TABLESPACE tamaris TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON tamaris; END;' ;

还是不行。对此我将不胜感激,谢谢。

编辑

建议的程序内容:

CREATE OR REPLACE
PROCEDURE CREATE_USER_IN_DB(p_username IN NVARCHAR2, p_password IN UTILISATEUR.passwordutilisateur%type, p_type IN NUMBER ) AS 
BEGIN
  EXECUTE IMMEDIATE 'CREATE USER '|| p_username ||' IDENTIFIED BY '|| p_password ||' DEFAULT TABLESPACE tamaris 
  TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON tamaris';

  IF p_type = 1 THEN
    EXECUTE IMMEDIATE 'GRANT Base_User TO '|| p_username;
  ELSE
    EXECUTE IMMEDIATE 'GRANT Adv_User TO '|| p_username;
  END IF;
END CREATE_USER_IN_DB;

EDIT2

我如何在触发器之外调用程序:

BEGIN 
CREATE_USER_IN_DB('whatever','quickpass', 2); 
END;

我明白了

ORA-00900: 
Invalid SQL instruction
ORA-06512: at "TAMARIS.CREATE_USER_IN_DB", line 3
ORA-06512: at line 2
00900. 00000 -  "invalid SQL statement"
*Cause:    
*Action:

【问题讨论】:

去掉 CREATE USER 语句连接末尾的分号,如果有帮助,请告诉我们。换句话说,QUOTA UNLIMITED ON tamaris 后面不应该有分号。此外,GRANT 语句可能不应该有尾随 '; END;' 它确实有帮助,但是现在我有“不可能在触发器中提交”。 当您尝试调试执行动态 SQL 的代码时,使用字符串填充局部变量(即l_sql_stmt)并打印该字符串(或将其写入表)通常很有帮助) 在执行之前。然后,您可以看到出现错误时生成的 SQL 语句。如果您将 SQL 语句复制并粘贴到 SQL*Plus(或您喜欢的工具)中并执行它,您通常会收到更有意义的错误消息。 我终于因为某种原因让它工作了......我做的最后一件事是将查询放入 varchar 并与 Execute Immediate 一起使用。谢谢。 【参考方案1】:

1) 正如@Bob Jarvis 建议的那样,当您构建一个打算传递给EXECUTE IMMEDIATE 的SQL 语句时,该SQL 语句不应包含尾随分号;

2) 由于CREATE USERGRANT 是DDL 语句,它们会在执行前后发出隐式提交。这意味着您不能在触发器中调用 DDL 语句,因为触发器不能导致事务结束。如果您真的想这样做(并且创建用户作为在表中插入行的副作用似乎是一个非常有问题的架构),您将不得不异步执行此操作。您的触发器可以调用DBMS_JOB 来安排作业在您当前事务完成后运行,并且该作业可以执行 DDL 语句。例如,如果您创建一个实际创建用户的过程(这是您所有 DDL 的所在)

CREATE PROCEDURE create_user( p_username IN NVARCHAR2, 
                              p_password IN UTILISATEUR.passwordutilisateur%type,
                              p_type     IN NUMBER )
AS
  <<implement procedure>>

那么你的触发器可以做类似的事情

CREATE OR REPLACE
TRIGGER UTILISATEUR_CREATE_USER_TRG 
AFTER INSERT ON UTILISATEUR 
FOR EACH ROW
DECLARE
  nom_compte NVARCHAR2(20 CHAR);
  str_create VARCHAR2(300);
  str_grant VARCHAR(250);
  type_compte NUMBER;
  l_jobno PLS_INTEGER;
  unauthorized_exception EXCEPTION;
BEGIN

  CASE
    WHEN :new.idtypecompte = 1 THEN
      nom_compte := :new.pseudoutilisateur;
      type_compte := 1;
    WHEN :new.idtypecompte = 2 THEN
      nom_compte := 'AC_'|| :new.pseudoutilisateur;
      type_compte := 2;
    WHEN :new.idtypecompte = 3 THEN
      RAISE unauthorized_exception;
  END CASE;

  dbms_job.submit( l_jobno,
                   'BEGIN create_user( ''' || nom_compte || ''', ' || 
                                       '''' || :new.passwordutilisateur || ''', ' || 
                                       type_compte || '); END;' );
END;

【讨论】:

难道不能在触发器中也添加一个简单的 Pragma AUTONOMOUS_TRANSACTION 吗? @Sebas - 可以编译。然而,它通常会导致更严重的问题。由于事务是自治的,无论表上的插入最终成功还是失败,都将创建用户。由于这是一个行级触发器,Oracle 可能需要多次执行它(中间有回滚)以保持写入一致性。如果必须这样做,则自治事务的工作将不会回滚,因此触发器将在第二次执行时生成异常,从而导致语句回滚。 dbms_job.submit 给了我一个错误: Erreur(24,19): PLS-00103: Symbol "`" continue avg count... @Shinosha - 抱歉,我在 BEGIN 之前有一个 ` 而不是 '。我修好了。 @Sebas - 我认为这就是答案。 AUTONOMOUS_TRANSACTION 是要走的路——尽管 Justin Cave 提出了一些问题——可能这应该放在 POST 触发器中以尝试缓解回滚问题。

以上是关于动态 SQL 和 ORA-00911的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 动态SQL 注意细节 ORA-00911: 无效字符

oracle: sql语句报ora-01461/ora-00911错误

Mybatis 批量更新 ORA-00911: 无效字符的错误

SQL%ROWCOUNT 产生 ORA-00911:无效字符

插入错误:java.sql.SQLException:ORA-00911:无效字符

java.sql.SQLException: ORA-00911: 无效字符