2010-08-25 6 views
1

Voici un scénario avec un produit que nous avons acheté. Le produit nous permet de créer des champs personnalisés, mais ces champs sont stockés en tant que ROWS dans une table CUSTOM.Assemblage de colonnes à des lignes

Je souhaite écrire une requête qui se connecte à plusieurs champs personnalisés et extrait une seule ligne. Laissez-moi vous donner un exemple.

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

Ma table personne a un enregistrement

1) 1001 - Clark Kent 

Disons que je crée deux champs personnalisés pour personne appelée âge et le poids. Dans ce cas, deux enregistrements seront créés dans la table CUSTOMFIELDS. Maintenant, les valeurs de ces champs personnalisés seront stockées dans la table CUSTOMFIELDVALUE comme ceci.

1) 100 - 100 - 1001 - 44 
2) 101 - 200 - 1001 - 200 lbs 

Je veux écrire une requête de sélection qui ira chercher le dossier comme celui-ci

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

Je pense comment cela peut être réalisé par pur SQL. Le nombre de champs personnalisés peut augmenter ou diminuer en fonction de la configuration du produit.

+2

D'après ce que j'ai appris (sur le SO), on appelle le modèle Entité-Attribut-Valeur (EAV). Diverses références sur SO, ou sur [Wikipedia] (http://en.wikipedia.org/wiki/Entity-attribute-value_model) ... – pascal

+1

Quelle version d'Oracle utilisez-vous? Oracle 11 introduit PIVOT, qui vous permet de convertir des lignes en colonnes avec un effort minimal. –

+0

Quelque chose ne va pas dans cet exemple - la troisième valeur dans la table CustomFieldValue est nommée cfFieldName et votre exemple montre 1001, le Person.ID? –

Répondre

0

C'est un problème intéressant. Vous voulez changer dynamiquement le nombre et le nom des colonnes. Ce n'est pas possible de créer avec du SQL "normal". J'ai essayé de créer un échantillon avec l'utilisation de PIPELINED FUNCTION.

j'ai créé la table:

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

...et je plaçais des données:

INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(100, 100, 1001, 44); 
INSERT INTO CUSTOMFIELDVALUE (CFVID, CFID, CFFieldName, CFFieldValue) values(101, 200, 1001, 200); 

Ensuite, j'ai créé un type d'objet:

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 
/

Et aussi créé FONCTION PIPELINE:

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; 

Que vous pouvez utiliser SQL pour obtenir des échantillons de données (note: empêcher l'exception ORA-22905, vous devez définir la variable de session "ALTER SESSION SET CURSOR_SHARING = EXACT;"):

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

Et voici la sortie:

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

Dans la première colonne est en-tête, où sont stockées des informations sur les noms de champs et après en-tête sont stockés des données. Vous pouvez utiliser la combinaison de ces SQLs:

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. Le premier argument est person_id
  2. deuxième argument est la liste des éléments que vous souhaitez voir la sortie
  3. si le premier argument est NULL, vous pouvez voir la liste de toutes les personnes
  4. si le deuxième argument est NULL, vous pouvez voir la liste de tous les arguments
  5. Script n'est pas complète, et ont des limites:
    1. dans l'objet CustomFieldType vous ne pouvez pas modifier dynamiquement nombre de champs (je veux dire fieldValue1, fieldValue2 ...)
    2. dans le corps de GET_PERSON_FIELDS fonction que vous pouvez voir, problème dynamique est également dans les instructions IF (IF (l_position = 1) ALORS l_data_row.fieldValue1, IF (l_position = 2) ALORS l_data_row.fieldValue1) ...

Enfin, quand je suis entré dans quelques exemples de données, comme celui-ci:

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

... et ra n cette commande SQL:

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

... sortie ressemblait à ceci:

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

Belle solution. Je vais explorer cela plus avant. – abhi

0

Quelque chose comme ça peut-être:

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; 

Son pas tout à fait clair pour moi que person.id joint à customfieldvalue.cfieldname mais vous ne disons 100 - 100 - 1001-1044 donc je suppose que vous essayez de dire 1001 est la troisième colonne? Essentiellement, la requête filtre la table customfieldvalues ​​et l'utilise comme deux tables distinctes. Ce que je pense est ce que vous voulez - votre question ne le rend pas très clair.

Questions connexes