2010-08-25 27 views
1

以下是我們購買產品的場景。該產品允許我們創建自定義字段,但這些字段在REST中存儲在CUSTOM表中。將列連接到行

我想寫一個將連接到多個自定義字段並獲取單個行的查詢。

讓我給你舉個例子。

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

我的人表有一個記錄

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 

我想寫一個選擇查詢,將獲取該記錄這樣

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

我在想如何能夠通過純SQL來實現。自定義字段的數量可以增加或減少,具體取決於產品的配置。

+2

從我學到了什麼(上SO),這就是所謂的實體 - 屬性 - 值(EAV)模型。關於SO的各種參考文獻,或[Wikipedia](http://en.wikipedia.org/wiki/Entity-attribute-value_model)... – pascal 2010-08-25 20:36:06

+1

您正在使用哪個版本的Oracle? Oracle 11引入了PIVOT,它允許您以最小的努力將行轉換爲列 – 2010-08-25 21:09:42

+0

此示例看起來有點不妥 - CustomFieldValue表中的第三個值名爲cfFieldName,您的示例顯示1001,Person.ID? – 2010-08-26 12:10:34

回答

0

這是一個有趣的問題。您想要動態更改列的編號和名稱。這是不可能用「普通」SQL創建的。我嘗試使用PIPELINED FUNCTION創建示例。

我首先創建表:

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; 

比你可以使用SQL來獲取樣本數據(注意:防止異常ORA-22905,你必須設置會話變量「ALTER SESSION SET CURSOR_SHARING = EXACT;」):

SELECT * FROM TABLE(GET_PERSON_FIELDS(1001,'AGE,WEIGHT')); 

這裏是輸出:

ROW_ID FIELDTYPE PERSON_ID FIELDVALUE FIELDVALUE FIELDVALUE 
------ ---------- --------- ---------- ---------- ---------- 
    1 HEADER    AGE 
    2 DATA   1001 44 

在第一列是首部,其中存儲關於字段名和報頭之後信息被存儲的數據。您可以使用這些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)); 
  1. 第一個參數是PERSON_ID
  2. 第二個參數是你想看到的輸出
  3. 如果第一個參數是NULL的項目列表中,你可以看到列表所有的人
  4. 如果第二個參數是NULL,你可以看到所有的參數
  5. 腳本是不完整的名單,並有一定的侷限性:
    1. 在對象CustomFieldType你不能動態地改變區域的數量(我的意思是fieldValue1,fieldValue2 ...)
    2. 在功能GET_PERSON_FIELDS的身體,你可以看到,動態問題也是IF語句(IF(l_position = 1) THEN l_data_row.fieldValue1,IF(l_position = 2)THEN 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'); 

...和ra n這個SQL命令:

SELECT * FROM TABLE(GET_PERSON_FIELDS(NULL,NULL)); 

...輸出是這樣的:

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 
+0

美麗的解決方案。我將進一步探索這一點。 – abhi 2010-08-26 19:06:59

0

像這樣的東西可能:

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年至1044年,所以我想你想說1001是第三列?本質上,查詢過濾customfieldvalues表並將其用作兩個單獨的表。我認爲你想要的是 - 你的問題並沒有說得很清楚。