【问题标题】:How can I make a function to return a constraint's column name(s)?如何使函数返回约束的列名?
【发布时间】:2020-10-10 16:43:49
【问题描述】:

我正在尝试在 Oracle 中创建一个 pl/SQL 函数,该函数返回受给定约束影响的列的列名。例如,给定下表:

CREATE TABLE Products
( Mfr         CHAR(3)   NOT NULL         
 ,Product     CHAR(5)   NOT NULL         
 ,Description VARCHAR2(100) DEFAULT 'N/A'
 ,Price       NUMBER(5,2)     
 ,QtyOnHand   NUMBER(5) DEFAULT 0
 ,CONSTRAINT  ProductsPK
    PRIMARY KEY(Mfr, Product)
 ,CONSTRAINT  UniqueDescription
    UNIQUE (Description)
 ,CONSTRAINT  UniqueProduct
    UNIQUE (Product)
 ,CONSTRAINT  CheckPrice
    CHECK (Price BETWEEN .01 AND 87.98)
 ,CONSTRAINT  CheckQuantity
    CHECK (QtyOnHand >= 0)
);

我想将 2 个值传递给一个函数,例如表名称(在本例中为“产品”)和约束名称(在本例中为 ProductsPK 注意:该函数应该能够处理任何给定约束)。鉴于这些详细信息,它应该返回受影响的列名。

截至目前,我的功能如下

SET SERVEROUTPUT ON;
CREATE OR REPLACE FUNCTION GET_CONSTRAINT_COLUMNS(
    iTableName IN Varchar2,
    iConstraintName IN Varchar2
)
RETURN varchar2
AS
        CURSOR Extract_KEY IS
        SELECT INDEX_NAME,a.constraint_type
        FROM USER_CONSTRAINTS a
        WHERE TABLE_NAME = iTableName;
        --ORDER BY Column_ID;
        CurrentRow Extract_KEY%ROWTYPE;
        
        --Declaring Variables
        wKeys VARCHAR2(50);
    BEGIN
        For CurrentRow IN Extract_Key LOOP
        
            IF currentRow.Constraint_type = 'P' THEN
            
                SELECT COLUMN_NAME
                INTO wkeys
                FROM  user_cons_columns 
                WHERE TABLE_NAME = iTableName 
                AND constraint_name = iconstraintname;
    
            ELSIF currentRow.Constraint_Type = 'U' THEN
            
                SELECT COLUMN_NAME 
                INTO wkeys
                FROM  user_cons_columns 
                WHERE constraint_name = iconstraintname 
                AND table_name = iTableName;
    
            END IF; 
            
        END LOOP;
    RETURN wKeys;
END;
/

上述函数中的选择语句单独工作正常。但是当函数本身通过下面的匿名块执行时:

SET SERVEROUTPUT ON;
DECLARE
    wconsumable varchar2(24);
    BEGIN
    wConsumable := GET_CONSTRAINT_COLUMNS('PRODUCTS',  'PRODUCTSPK');
    DBMS_OUTPUT.PUT_LINE(wConsumable);
    END;
    /

我收到以下错误:

Error starting at line : 47 in command -
DECLARE
    wconsumable varchar2(24);
    BEGIN
    wConsumable := GET_CONSTRAINT_COLUMNS('PRODUCTS',  'PRODUCTSPK');
    DBMS_OUTPUT.PUT_LINE(wConsumable);
    END;
Error report -
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "T325.GET_CONSTRAINT_COLUMNS", line 21
ORA-06512: at line 4
01422. 00000 -  "exact fetch returns more than requested number of rows"
*Cause:    The number specified in exact fetch is less than the rows returned.
*Action:   Rewrite the query or change number of rows requested

其他信息:

上述函数中的主键选择语句(begin 之后的第一个)返回以下 IF RAN INDIVIDUALLY

MFR
PRODUCT

如果像这样单独运行,第二个选择语句(关于唯一键):

SELECT COLUMN_NAME FROM  user_cons_columns WHERE constraint_name = 'UNIQUEPRODUCT' AND table_name = 'PRODUCTS';

返回这个:

Product

如您所见,某些约束可能会返回 1 或 2 列。不管有多少列,我都希望它们都存储在变量中并返回到调用函数的过程。

我怀疑我的问题源于我正在使用的光标,但我不能确定。我应该如何解决这个问题?

【问题讨论】:

    标签: oracle plsql stored-functions


    【解决方案1】:

    您可以使用SYS_REFCURSOR 代替标准的内部数据类型,例如您的情况下的VARCHAR2,以便能够返回多行。带有 INTO 子句的 SELECT 语句不能返回多行,并且当前错误消息 (ORA-01422) 会出现。

    因此,首先创建一个包含SYS_REFCURSOR 的存储函数:

    SQL> CREATE OR REPLACE FUNCTION GET_CONSTRAINT_COLUMNS(iTableName      IN VARCHAR2,
                                                           iConstraintName IN VARCHAR2)
                             RETURN SYS_REFCURSOR AS
      wkeys SYS_REFCURSOR;
      v_sql VARCHAR2(32767);
    BEGIN
    
      v_sql := 'SELECT column_name
                  FROM user_cons_columns
                 WHERE constraint_name = :ic
                   AND table_name = :it';
    
      OPEN wkeys FOR v_sql USING iconstraintname, iTableName;
      RETURN wkeys;
    END;
    /
    

    然后从 SQL Developer 的控制台调用

    SQL> DECLARE
        wConsumable SYS_REFCURSOR;
    BEGIN
       :wConsumable := GET_CONSTRAINT_COLUMNS('PRODUCTS',  'PRODUCTSPK');
    END;
    /
    
    SQL> PRINT wConsumable ;
    
    • 第一个 SQL(为CURSOR Extract_KEY 准备)是多余的;
    • LOOP 中的两个 SELECT 语句之间没有区别, 顺便说一句,使用当前案例不需要LOOP
    • 命令PRINT可以代替DBMS_OUTPUT.PUT_LINE在 为了返回 SYS_REFCURSOR 的结果。

    【讨论】:

      【解决方案2】:

      我真的不知道您将使用这些功能的用例。 @Barbaros Özhan 提供的答案非常好,您可以将输出用作输入或对它进行任何类型的操作(如果有的话)

      但如果仅用于显示目的,我想提供另一种使用 listagg 的替代方法,它将列与用户定义的分隔符结合起来,并为您提供所有列的串联字符串。

      在我输入代码之前,我想指出您不需要任何 cursorif-else,因为您将 constraint_name 作为参数传递并使用 user_* 视图和约束在schema 中是唯一的。即使我们不需要table_name,但我们可以保留它以用于显示目的。

      所以我最终修改的代码是,

      CREATE OR REPLACE FUNCTION GET_CONSTRAINT_COLUMNS(
          iTableName IN Varchar2,
          iConstraintName IN Varchar2
      )
      RETURN varchar2
      AS
        --Declaring Variables
        wKeys VARCHAR2(4000);
      BEGIN
        SELECT listagg(column_name,' and ') within group (order by position)
          INTO wkeys
          FROM user_cons_columns 
         WHERE TABLE_NAME = iTableName 
           AND constraint_name = iconstraintname;
        RETURN wKeys;
      END;
      /
      

      -----测试------

      DECLARE
        wconsumable varchar2(24);
      BEGIN
        DBMS_OUTPUT.PUT_LINE('');
        wConsumable := GET_CONSTRAINT_COLUMNS
          ('PRODUCTS',  'PRODUCTSPK');
        DBMS_OUTPUT.PUT_LINE(wConsumable);
      END;
      /
      
      dbms_output:
      MFR and PRODUCT
      

      【讨论】:

        猜你喜欢
        • 2018-05-11
        • 2021-12-25
        • 2021-07-11
        • 1970-01-01
        • 2015-02-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多