Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/oracle/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Oracle 将列连接到行_Oracle_Database Design - Fatal编程技术网

Oracle 将列连接到行

Oracle 将列连接到行,oracle,database-design,Oracle,Database Design,下面是我们购买的产品的一个场景。该产品允许我们创建自定义字段,但这些字段作为行存储在自定义表中 我想编写一个查询,该查询将连接到多个自定义字段并获取一行 让我给你举个例子 1) PERSON TABLE (ID int, NAME varchar2(30)); 2) CUSTOMFIELDS TABLE(CFID int, CFTable varchar2(30), CFFieldName varchar2(30), CFFieldType varchar2(30)); 3) CUSTOMFIE

下面是我们购买的产品的一个场景。该产品允许我们创建自定义字段,但这些字段作为行存储在自定义表中

我想编写一个查询,该查询将连接到多个自定义字段并获取一行

让我给你举个例子

1) PERSON TABLE (ID int, NAME varchar2(30));
2) CUSTOMFIELDS TABLE(CFID int, CFTable varchar2(30), CFFieldName varchar2(30), CFFieldType varchar2(30));
3) CUSTOMFIELDVALUE TABLE(CFVID int, CFID int, CFFieldName varchar2(100), CFFieldValue varchar2(100));
我的person表有一条记录

1) 1001 - Clark Kent
假设我为个人创建了两个名为“年龄”和“体重”的自定义字段。 在这种情况下,将在CUSTOMFIELDS表中创建两条记录

1) 100 - PERSON - AGE - INTEGER
2) 200 - PERSON - WEIGHT - INTEGER
现在,这些自定义字段的值将存储在CUSTOMFIELDVALUE表中,如下所示

1) 100 - 100 - 1001 - 44
2) 101 - 200 - 1001 - 200 lbs
我想写一个select查询,它将像这样获取记录

PERSON, AGE , WEIGHT
Clark Kent, 44, 200 lbs

我正在思考如何通过纯SQL实现这一点。自定义字段的数量可以增加或减少,具体取决于产品的配置。

类似于以下内容:

select p.name, age.CFFieldValue, weight.CFFieldValue
from person p
inner join CUSTOMFIELDVALUE age
 on p.id = age.CFFieldName
inner join CUSTOMFIELDS age_f
 on age_f.cfid = age.cfid and age_f.CFFieldName = 'AGE' and age_f.CFTable = 'PERSON'
inner join CUSTOMFIELDVALUE weight
 on p.id = weight.CFFieldName and weight_f.CFFieldName = 'AGE' and weight_f.CFTable = 'PERSON'
inner join CUSTOMFIELDS weight_f
 on weight_f.cfid = weight.cfid;

我不太清楚person.id是否加入customfieldvalue.cfieldname,但你说的是100-100-1001-44,所以我猜你是想说1001是第三列?本质上,查询过滤customfieldvalues表,并将其用作两个单独的表。我想这就是你想要的——你的问题并没有说得很清楚。

这是一个有趣的问题。您希望动态更改列的数量和名称。这是不可能用“普通”SQL创建的。我尝试使用流水线函数创建示例

我首先创建了一个表:

CREATE TABLE PERSON (ID int, NAME varchar2(30));
CREATE TABLE CUSTOMFIELDS (CFID int, CFTable varchar2(30), CFFieldName varchar2(30), CFFieldType varchar2(30));
CREATE TABLE CUSTOMFIELDVALUE (CFVID int, CFID int, CFFieldName varchar2(100), CFFieldValue varchar2(100));

INSERT INTO PERSON(id, name) values(1001, 'Clark Kent');
INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(100, 'PERSON', 'AGE', 'INTEGER');
INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(200, 'PERSON', 'WEIGHT', 'INTEGER');
…我放置了一些数据:

INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(100, 100, 1001, 44);
INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(101, 200, 1001, 200);
然后我创建了一个对象类型:

CREATE TYPE CustomFieldType AS OBJECT
(
  row_id number,
  fieldType varchar2(200),
  person_id number,
  fieldValue1 varchar2(2000),
  fieldValue2 varchar2(2000),
  fieldValue3 varchar2(2000),
  fieldValue4 varchar2(2000),
  fieldValue5 varchar2(2000)
)
/

CREATE TYPE CustomFieldTypeSet AS TABLE OF CustomFieldType
/
并创建了流水线功能:

CREATE OR REPLACE
    FUNCTION GET_PERSON_FIELDS(person_id_in   IN NUMBER
                                    ,field_names_in IN VARCHAR2) RETURN CustomFieldTypeSet
        PIPELINED
    IS

        -- constructor CustomFieldType()
        l_header_row          CustomFieldType := CustomFieldType(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
        l_data_row            CustomFieldType := CustomFieldType(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
        l_tablen              BINARY_INTEGER;
        l_tab                 DBMS_UTILITY.uncl_array;
        l_num_of_field_values PLS_INTEGER := 5;
        l_counter             PLS_INTEGER := 1;
        l_position            PLS_INTEGER;
        l_field_names_in      VARCHAR2(2000) := field_names_in;

        TYPE type_header_hash IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(200);
        l_header_hash type_header_hash;

    BEGIN

        -- 1) check, what fields you can display

        IF (l_field_names_in IS NULL) THEN

            <<get_all_fields>>
            FOR cur_all_fields IN  (SELECT DISTINCT flds.CFFIELDNAME
                                               FROM CUSTOMFIELDS flds
                                                   ,CUSTOMFIELDVALUE cfv
                                              WHERE cfv.CFID = flds.CFID
                                                AND flds.CFTable = 'PERSON' ) LOOP
                l_field_names_in := l_field_names_in ||
                                    cur_all_fields.CFFIELDNAME ||
                                    ',';
            END LOOP get_all_fields;

        END IF;

        -- 2) generate header (function RTRIM prevent ORA-00931 exception!)

        DBMS_UTILITY.comma_to_table(list => RTRIM(l_field_names_in, ','), tablen => l_tablen, tab => l_tab);
        l_header_row.row_id := 1;
        l_header_row.fieldType := 'HEADER';

        <<header_cursor>>
        FOR i IN 1..l_tablen LOOP

            IF (i = 1) THEN
                l_header_row.fieldValue1 := l_tab(i);
                l_header_hash(l_tab(i)) := i;
            ELSIF (i = 2) THEN
                l_header_row.fieldValue2 := l_tab(i);
                l_header_hash(l_tab(i)) := i;
            ELSIF (i = 3) THEN
                l_header_row.fieldValue3 := l_tab(i);
                l_header_hash(l_tab(i)) := i;
            ELSIF (i = 4) THEN
                l_header_row.fieldValue4 := l_tab(i);
                l_header_hash(l_tab(i)) := i;
            ELSIF (i = 5) THEN
                l_header_row.fieldValue5 := l_tab(i);
                l_header_hash(l_tab(i)) := i;
            END IF;

        END LOOP header_cursor;

        -- 3) print data to SQL (over pipe)...

        PIPE ROW(l_header_row);

        FOR cur_persons IN  (SELECT ID
                               FROM PERSON
                              WHERE ID = COALESCE(person_id_in, ID) ) LOOP
            l_data_row.row_id := NULL;
            l_data_row.person_id := NULL;
            l_data_row.fieldType := NULL;
            l_data_row.fieldValue1 := NULL;
            l_data_row.fieldValue2 := NULL;
            l_data_row.fieldValue3 := NULL;
            l_data_row.fieldValue4 := NULL;
            l_data_row.fieldValue5 := NULL;
            l_data_row.fieldType := 'DATA';
            FOR cur_data IN  (SELECT p.ID AS person_id
                                    ,cfv.CFID
                                    ,flds.CFTABLE
                                    ,flds.CFFIELDNAME
                                    ,cfv.CFFIELDVALUE
                                FROM PERSON p
                                    ,CUSTOMFIELDS flds
                                    ,CUSTOMFIELDVALUE cfv
                               WHERE p.ID = cur_persons.ID
                                 AND p.ID = cfv.CFFIELDNAME
                                 AND cfv.CFID = flds.CFID ) LOOP
                l_data_row.person_id := cur_persons.ID;
                l_position := NULL;

                IF (l_header_hash.EXISTS(cur_data.CFFIELDNAME)) THEN
                    l_position := l_header_hash(cur_data.CFFIELDNAME);
                END IF;

                IF (l_position = 1) THEN
                    l_data_row.fieldValue1 := cur_data.CFFIELDVALUE;
                ELSIF (l_position = 2) THEN
                    l_data_row.fieldValue2 := cur_data.CFFIELDVALUE;
                ELSIF (l_position = 3) THEN
                    l_data_row.fieldValue3 := cur_data.CFFIELDVALUE;
                ELSIF (l_position = 4) THEN
                    l_data_row.fieldValue4 := cur_data.CFFIELDVALUE;
                ELSIF (l_position = 5) THEN
                    l_data_row.fieldValue5 := cur_data.CFFIELDVALUE;
                END IF;

            END LOOP;
            l_counter := l_counter + 1;
            l_data_row.row_id := l_counter;
            PIPE ROW(l_data_row);
        END LOOP;

        RETURN;
    END GET_PERSON_FIELDS;
这里是输出:

ROW_ID FIELDTYPE  PERSON_ID FIELDVALUE FIELDVALUE FIELDVALUE
------ ---------- --------- ---------- ---------- ----------
     1 HEADER               AGE
     2 DATA            1001 44
在第一列中是header,其中存储关于字段名的信息,在header之后存储数据。您可以使用以下SQL的组合:

SELECT * FROM TABLE(GET_PERSON_FIELDS(1001,'AGE,WEIGHT'));
SELECT * FROM TABLE(GET_PERSON_FIELDS(1002,'AGE,GENDER'));
SELECT * FROM TABLE(GET_PERSON_FIELDS(1001,NULL));
SELECT * FROM TABLE(GET_PERSON_FIELDS(NULL,NULL));
  • 第一个参数是person\u id
  • 第二个参数是要查看输出的项目列表
  • 若第一个参数为NULL,则可以看到所有人员的列表
  • 若第二个参数为NULL,则可以看到所有参数的列表
  • 脚本不完整,存在一些限制:
  • 在对象CustomFieldType中,不能动态更改字段数(我指的是fieldValue1、fieldValue2…)
  • 如您所见,在函数体GET_PERSON_字段中,IF语句也存在动态问题(IF(l_position=1)然后l_data_row.fieldValue1,IF(l_position=2)然后l_data_row.fieldValue1)
  • 最后,当我输入一些示例数据时,如下所示:

    INSERT INTO PERSON(id, name) values(1002, 'Lois Lane');
    INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(300, 'PERSON', 'GENDER', 'VARCHAR');
    INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(400, 'PERSON', 'SINGLE', 'VARCHAR');
    INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(102, 100, 1002, 45);
    INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(103, 300, 1002, 'FEMALE');
    INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(104, 400, 1002, 'YES');
    
    ROW_ID FIELDTYPE  PERSON_ID FIELDVALUE FIELDVALUE FIELDVALUE FIELDVALUE
    ------ ---------- --------- ---------- ---------- ---------- ----------
         1 HEADER               AGE        GENDER     SINGLE     WEIGHT
         2 DATA            1001 44                               200
         3 DATA            1002 45         FEMALE     YES
    
    。。。并运行以下SQL命令:

    SELECT * FROM TABLE(GET_PERSON_FIELDS(NULL,NULL));
    
    。。。输出如下所示:

    INSERT INTO PERSON(id, name) values(1002, 'Lois Lane');
    INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(300, 'PERSON', 'GENDER', 'VARCHAR');
    INSERT INTO CUSTOMFIELDS(CFID, CFTable, CFFieldName, CFFieldType) values(400, 'PERSON', 'SINGLE', 'VARCHAR');
    INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(102, 100, 1002, 45);
    INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(103, 300, 1002, 'FEMALE');
    INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(104, 400, 1002, 'YES');
    
    ROW_ID FIELDTYPE  PERSON_ID FIELDVALUE FIELDVALUE FIELDVALUE FIELDVALUE
    ------ ---------- --------- ---------- ---------- ---------- ----------
         1 HEADER               AGE        GENDER     SINGLE     WEIGHT
         2 DATA            1001 44                               200
         3 DATA            1002 45         FEMALE     YES
    

    从我所学到的(如此),这被称为实体属性值(EntityAttribute-Value,EAV)模型。您使用的是哪一版本的Oracle?Oracle 11引入了PIVOT,它允许您以最轻松的方式将行转换为列此示例似乎有点问题--CustomFieldValue表中的第三个值名为cfFieldName,您的示例显示1001,Person.ID?@Mark:我们使用的是9i。我们将很快转向10G@达米尔:我不知道你的意思。personID是行和自定义字段值之间的链接。我认为这是一个遗留应用程序,因此您无法对此采取任何措施,但这似乎是糟糕的产品设计,会破坏性能和可读性。很少有EAV有用的情况,就像在数据库中构建数据库一样。如果您已经知道这些字段,只需创建它们,如果不使用,则将其保留为空,因为我怀疑您的字段数是否超过255个;)。不管怎么说,这只是我的2美分。很好的解决方案。我将进一步探讨这一点。