如何为 return 约束的列名创建一个函数?

How can I make a function to return a constraint's column name(s)?

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

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 个值传递给函数,例如 Table 名称(在本例中为 'Products')和约束的名称(在本例中为 ProductsPK 注意: 该函数应该能够处理任何给定的约束)。鉴于这些细节,它应该 return 受影响的列名称。

目前我的功能如下

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;
/

上述函数中的 select 语句单独运行良好。但是当函数本身通过下面的匿名块执行时:

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

附加信息:

主键 select 语句(begin 之后的第一个语句)在上面的函数中 returns 下面的 IF 运行 INDIVIDUALLY

MFR
PRODUCT

第二个select语句(关于唯一键)IF 运行 INDIVIDUALLY 像这样:

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

Returns这个:

Product

如您所见,某些约束可能 return 1 或 2 列。无论列数如何,我都希望它们都存储在变量中并 returned 到调用函数的过程。

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

您可以使用 SYS_REFCURSOR 而不是标准内部数据类型,例如 VARCHAR2,以便能够 return 多行。带有 INTO 子句的 SELECT 语句不能 return 多行,并且当前错误消息 (ORA-01422) hurls.

因此,先创建一个包含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 开发者控制台调用

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 order to return 结果 SYS_REFCURSOR.

我真的不知道您将使用这些功能的用例。 @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