您可以将 CREATE TABLE 语句放在 PL/SQL 块中吗?

Posted

技术标签:

【中文标题】您可以将 CREATE TABLE 语句放在 PL/SQL 块中吗?【英文标题】:Can you put a CREATE TABLE statement inside a PL/SQL block? 【发布时间】:2017-12-15 21:34:02 【问题描述】:

我正在尝试编写一些代码以在创建表之前检查表是否存在。首先,我尝试运行以下命令:

BEGIN 
create table IT.DEVOPS_UC_SERVER_REG_TABLE
(
  agentname        VARCHAR2(100) not null,
  servername       VARCHAR2(100) not null,
  datelastdeployed TIMESTAMP(6) WITH TIME ZONE,
  technology       VARCHAR2(100) not null,
  issetup          VARCHAR2(10)
);
EXCEPTION
  NULL;
END;/

但是,这似乎不是有效的 PL/SQL 代码。有谁知道为什么会这样? PL/SQL 的 DDL 和 DML 有区别吗?

【问题讨论】:

exception 块格式错误。如果你的意思是,... exception when others then null; ...,请停止这样做! PL/SQL 只会解析 DML,不会解析 DDL。所以 DDL 需要被包裹在一个字符串中并传递给 SQL 引擎。 EXECUTE IMMEDIATE 和 DBMS_SQL 是这样做的两种方式。 在 plsql 块中创建表的需要不是常见的要求。 ddl 通常是自己创建和执行的。 s.o.上有几个例子。如果您需要在 plsql 块中执行此操作。 是的,但为什么?有很多更简单的方法。您指定 sqldeveloper,因此展开表列表并查看您想要的表是否在那里;使用 DESC 命令;从 ..._tables 视图中选择表名;只需运行创建 - 如果存在,您将收到错误。 感谢 Shannon 和 Patrick 回答“为什么”。作为我们自动化计划的一部分,我需要这样做,但我同意,在我看来,这绝对违反了行业惯例 【参考方案1】:

是的,您可以使用动态 SQL 来完成,即立即执行。

但是,请三思 - 以这种方式创建表格并不常见。你为什么要这样做?

【讨论】:

【参考方案2】:

你应该使用这样的块:

DECLARE 
  L_CNT PLS_INTEGER;
BEGIN
  SELECT COUNT(0)
  INTO L_CNT
  FROM ALL_TABLES T
  WHERE T.TABLE_NAME = 'DEVOPS_UC_SERVER_REG_TABLE'
      AND T.OWNER = 'IT';

  IF L_CNT > 0 THEN
    RETURN;
  END IF;

EXECUTE IMMEDIATE 'create table IT.DEVOPS_UC_SERVER_REG_TABLE' || chr(10) ||
                    '(' || chr(10) ||
                    '  agentname        VARCHAR2(100) not null,' || chr(10) ||
                    '  servername       VARCHAR2(100) not null,' || chr(10) ||
                    '  datelastdeployed TIMESTAMP(6) WITH TIME ZONE,' || chr(10) ||
                    '  technology       VARCHAR2(100) not null,' || chr(10) ||
                    '  issetup          VARCHAR2(10)' || chr(10) ||
                    ')';

END;

【讨论】:

好建议,使用“all_tables” 所有答案都很棒,我都投了赞成票,但我喜欢这个答案,因为它以一种非常优雅的方式绕过了异常并解释了有关 ALL_TABLES 的内容【参考方案3】:

oracle 抛出的常见异常是“ORA-00955:名称已被现有对象使用”。但仅此一项不足以说明该表已经创建。由于在同一个命名空间内,没有两个对象可以具有相同的名称:

https://docs.oracle.com/database/121/SQLRF/sql_elements008.htm#i78631

因此出现异常消息:“名称已被现有对象使用”

但如果您知道这一点并想要一个匿名的 pl/sql 块:

declare
  lc__    constant varchar2(100) := 'Anonymous PL/SQL Block';
  already_created  exception;
  pragma exception_init(already_created, -955);  -- ORA-00955: name is already used by an existing object
  lv_stmt          varchar2(32767);
begin
  lv_stmt := q'[create table it.devops_uc_server_reg_table (
                  agentname        varchar2(100) not null,
                  servername       varchar2(100) not null,
                  datelastdeployed timestamp(6) with time zone,
                  technology       varchar2(100) not null,
                  issetup          varchar2(10))
               ]';
  begin
    execute immediate lv_stmt;
  exception
    when already_created then
      dbms_output.put_line('Name already defined on an existing object');
    when others then
      raise;
  end;
exception when others then
  raise_application_error(-20777, lc__ || chr(10) || dbms_utility.format_error_stack);
end;

【讨论】:

【参考方案4】:

感谢所有出色的答案。我决定将它们结合起来,这就是我最终使用的:

DECLARE 
  L_CNT PLS_INTEGER;
  lv_stmt varchar2(32767);
BEGIN
  --Check if the table already exists 
  SELECT COUNT(0)
  INTO L_CNT
  FROM ALL_TABLES T
  WHERE T.TABLE_NAME = 'DEVOPS_UC_SERVER_REG_TABLE'
      AND T.OWNER = 'IT';
  IF L_CNT > 0 THEN
     dbms_output.put_line('The table already exists');
    RETURN;
  END IF;

  --Check if the constraint already exists
  SELECT COUNT(0)
  INTO L_CNT
  FROM ALL_CONSTRAINTS C
  WHERE C.CONSTRAINT_NAME = 'DEVOPS_UC_Server_Reg_PKEY'
      AND C.OWNER = 'IT';
  IF L_CNT > 0 THEN
     dbms_output.put_line('The primary key constraint already exists');
    RETURN;
  END IF;

  dbms_output.put_line('Creating table...');


  /*
 this code is auto-generated from DBMS_METADATA. It was accessed using the following query:

SELECT 
DBMS_METADATA.GET_DDL( 'TABLE','DEVOPS_UC_SERVER_REG_TABLE','IT') 
FROM DUAL;

 so long as the tables do not exist, and the PRIMARY KEY NAME does not already exist 
 then this will create the table with the primary key constraint. 
 otherwise, you will run into ORA-0095: name already used by existing object (if table exists)
 or ORA-02264: name already used by an existing constraint
 TODO: make the table name and primary key constraint name variables for ease of use/robustness 
*/
  lv_stmt:=q'[
  CREATE TABLE "IT"."DEVOPS_UC_SERVER_REG_TABLE" 
   (    "AGENTNAME" VARCHAR2(100 CHAR) NOT NULL ENABLE, 
    "SERVERNAME" VARCHAR2(100 CHAR) NOT NULL ENABLE, 
    "DATELASTDEPLOYED" TIMESTAMP (6) WITH TIME ZONE, 
    "TECHNOLOGY" VARCHAR2(100 CHAR) NOT NULL ENABLE, 
    "ISSETUP" VARCHAR2(10 CHAR), 
     CONSTRAINT "DEVOPS_UC_Server_Reg_PKEY" PRIMARY KEY ("AGENTNAME", "SERVERNAME", "TECHNOLOGY")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS 
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "IT"  ENABLE
   ) SEGMENT CREATION IMMEDIATE 
  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 
 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
  BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
  TABLESPACE "IT" ]';

    execute immediate lv_stmt;
dbms_output.put_line('Table created successfully');

END;

【讨论】:

【参考方案5】:

对于那些具有 MSSQL 背景的人来说,您可能遇到过一两种情况,您希望 Oracle 有一个与 IF NOT EXISTS() CREATE TABLE() 语法等效的情况。我的版本需要在 CRUSHER 模式中创建一个名为 A_OBF_Key 的表。另一个正在发布的版本也想创建同一个表(具有相同的结构)。我们不知道首先部署哪个版本,坦率地说,这并不重要。我们不想在两个版本之间引入依赖关系,但客户不希望看到第二个部署的版本出现“TABLE ALREADY EXISTS”错误。

对于那些对EXECUTE IMMEDIATE 持怀疑态度的人,有一种方法可以使用 Oracle 元数据、一个 SQLPlus 变量和两个文件为 SQLPlus 编写脚本:一个在表存在时执行,一个在表存在时执行不存在。

我已经有一个名为“OBF_Key.sql”的文件,它创建了 A_OBF_Key 表:

/* OBF_Key.sql */

PROMPT Handle OBF_Key objects...
    PROMPT ... table
        CREATE TABLE CRUSHER.A_OBF_Key(
            KEY_NAME            VARCHAR2(200) NOT NULL,
            KEY_VALUE           VARCHAR2(500) NOT NULL,
            DESCRIPTION         VARCHAR2(500) ,
            SITE_CD             VARCHAR2(3) NOT NULL,
            EFFECTIVE_DTE       DATE NOT NULL,
            EXPIRE_DTE          DATE ,
            DATA_ENTRY_USER_ID  VARCHAR2(30) NOT NULL,
            DATA_ENTRY_DTE      DATE DEFAULT SYSDATE NOT NULL
        );

我也已经有一个名为“run_me_first.sql”的协调文件,该文件名为 OBF_Key.sql。我创建了一个附加文件“OBF_Key_Exists.sql”来处理已经创建 A_OBF_Key 的情况:

/* OBF_Key_Exists.sql */

PROMPT ...something else already installed OBF_Key, so moving on...

然后我将 run_me_first.sql 编辑为

    设置定义 设置扫描 声明一个 sqlplus 变量 根据 A_OBF_Key 的存在填充该变量 将脚本执行从硬编码的“OBF_Key.sql”更改为步骤 4 中分配的变量的值 设置扫描关闭 设置定义关闭

像这样:

/* run_me_first.sql */

SET DEFINE ON;
SET scan on;

COLUMN script_to_run NEW_VALUE script_to_run_now

PROMPT Check OBF_Key objects...
    /* will return 'OBF_Key_Exists.sql' if the table is already created, or 'OBF_Key.sql' if it is not yet created */
    SELECT CASE WHEN COUNT(*) > 0 THEN 'OBF_Key_Exists.sql' ELSE 'OBF_Key.sql' END AS script_to_run
    FROM All_Tables
    WHERE UPPER(owner) = 'CRUSHER'
        AND UPPER(table_name) = 'A_OBF_Key';

    --run the file named after the variable populated with the above query
    @@&script_to_run_now

    --these things I want to do whether or not the table already existed
    GRANT SELECT ON CRUSHER.A_OBF_Key TO SECURITY;
    GRANT SELECT ON CRUSHER.OBF_Key TO SECURITY;
    CREATE OR REPLACE SYNONYM SECURITY.OBF_Key FOR CRUSHER.OBF_Key;

--put session settings back the way we started
SET scan off;
SET DEFINE OFF;

【讨论】:

以上是关于您可以将 CREATE TABLE 语句放在 PL/SQL 块中吗?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在 db2 中的 create table 语句本身中定义索引?

pl/sql-create trigger on a table - 每当在同一个表中插入新记录时,应更新表的另一列

我可以在调用同一过程后将 PL/SQL 过程放在匿名块中吗?

oracle中,用create table ... as select * from table_a...语句备份或者其他用途会不会产生归档日志,

是否可以回滚主要 SQL 数据库中的 CREATE TABLE 和 ALTER TABLE 语句?

Oracle SQL:案例语句