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));
- Le premier argument est person_id
- deuxième argument est la liste des éléments que vous souhaitez voir la sortie
- si le premier argument est NULL, vous pouvez voir la liste de toutes les personnes
- si le deuxième argument est NULL, vous pouvez voir la liste de tous les arguments
- Script n'est pas complète, et ont des limites:
- dans l'objet CustomFieldType vous ne pouvez pas modifier dynamiquement nombre de champs (je veux dire fieldValue1, fieldValue2 ...)
- 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
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
Quelle version d'Oracle utilisez-vous? Oracle 11 introduit PIVOT, qui vous permet de convertir des lignes en colonnes avec un effort minimal. –
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? –