2009-06-10 11 views
2

J'essaye de construire une requête telle que certaines colonnes soient construites à partir d'une ligne précédente. Par exemple avec les données suivantes:Oracle SQL Query (Analytics?)

CREATE TABLE TEST (SEQ NUMBER, LVL NUMBER, DESCR VARCHAR2(10)); 
INSERT INTO TEST VALUES (1, 1, 'ONE'); 
INSERT INTO TEST VALUES (2, 2, 'TWO1'); 
INSERT INTO TEST VALUES (3, 2, 'TWO2'); 
INSERT INTO TEST VALUES (4, 3, 'THREE1'); 
INSERT INTO TEST VALUES (5, 2, 'TWO3'); 
INSERT INTO TEST VALUES (6, 3, 'THREE2'); 
COMMIT 

Je souhaite récupérer les données suivantes.

SEQ L1 L2 L3 
1 ONE NULL NULL 
2 ONE TWO1 NULL 
3 ONE TWO2 NULL 
4 ONE TWO2 THREE1 
5 ONE TWO3 THREE1 
5 ONE TWO3 THREE2 

-à-dire pour la ligne 3, il a lui-même la valeur de L2, L1 pour qu'il doit se rendre à la dernière ligne qui contient les données L1, dans ce cas, la première rangée.

J'ai essayé d'analyser l'analyse et la clause connect, mais je n'arrive pas à trouver une solution.
Des idées?

Répondre

5

Mise à jour: il y a une solution beaucoup plus simple que ma première réponse. Il est plus lisible et plus élégant, je vais donc le mettre ici en premier (Comme souvent, grâce à Tom Kyte):

SQL> SELECT seq, 
    2   last_value(CASE 
    3      WHEN lvl = 1 THEN 
    4      descr 
    5     END IGNORE NULLS) over(ORDER BY seq) L1, 
    6   last_value(CASE 
    7      WHEN lvl = 2 THEN 
    8      descr 
    9     END IGNORE NULLS) over(ORDER BY seq) L2, 
10   last_value(CASE 
11      WHEN lvl = 3 THEN 
12      descr 
13     END IGNORE NULLS) over(ORDER BY seq) L3 
14 FROM TEST; 

     SEQ L1   L2   L3 
---------- ---------- ---------- ---------- 
     1 ONE     
     2 ONE  TWO1  
     3 ONE  TWO2  
     4 ONE  TWO2  THREE1 
     5 ONE  TWO3  THREE1 
     6 ONE  TWO3  THREE2 

Après ma solution initiale:

SQL> SELECT seq, 
    2   MAX(L1) over(PARTITION BY grp1) L1, 
    3   MAX(L2) over(PARTITION BY grp2) L2, 
    4   MAX(L3) over(PARTITION BY grp3) L3 
    5 FROM (SELECT seq, 
    6     L1, MAX(grp1) over(ORDER BY seq) grp1, 
    7     L2, MAX(grp2) over(ORDER BY seq) grp2, 
    8     L3, MAX(grp3) over(ORDER BY seq) grp3 
    9    FROM (SELECT seq, 
10       CASE WHEN lvl = 1 THEN descr END L1, 
11       CASE WHEN lvl = 1 AND descr IS NOT NULL THEN ROWNUM END grp1, 
12       CASE WHEN lvl = 2 THEN descr END L2, 
13       CASE WHEN lvl = 2 AND descr IS NOT NULL THEN ROWNUM END grp2, 
14       CASE WHEN lvl = 3 THEN descr END L3, 
15       CASE WHEN lvl = 3 AND descr IS NOT NULL THEN ROWNUM END grp3 
16      FROM test)) 
17 ORDER BY seq; 

     SEQ L1   L2   L3 
---------- ---------- ---------- ---------- 
     1 ONE     
     2 ONE  TWO1  
     3 ONE  TWO2  
     4 ONE  TWO2  THREE1 
     5 ONE  TWO3  THREE1 
     6 ONE  TWO3  THREE2 
+0

C'est vraiment gentil, exactement ce que je cherchais, je soupçonnais que cela pouvait se faire via l'analytique, merci beaucoup. Merci également à Rax et Calmar pour leur contribution. – Patrick

+0

Oui, beaucoup beaucoup mieux, merci. Oui, j'avais deviné que vous étiez aussi un fan de Tom Kyte du style de la première réponse. – Patrick

0

Avez-vous seulement 3 niveaux (ou un nombre fixe de niveaux)?

Si oui, vous pouvez utiliser quelque chose comme ça, ce qui est très inefficace, mais je crois que des œuvres (je ne peux pas l'exécuter à partir de cet ordinateur il est donc un code « aveugle » qui peut nécessiter de légères modifications):

SELECT COUNTER.SEQ AS SEQ, A.DESCR AS L1, B.DESCR AS L2, C.DESCR AS L3 
FROM TABLE AS COUNTER, TABLE AS A, TABLE AS B, TABLE AS C 
WHERE 
A.SEQ = 
    (SELECT MAX(D.SEQ) FROM TABLE AS D 
    WHERE D.LVL = 1 AND D.SEQ <= COUNTER.SEQ) AND 
B.SEQ = 
    (SELECT MAX(D.SEQ) FROM TABLE AS D 
    WHERE D.LVL = 2 AND D.SEQ <= COUNTER.SEQ) AND 
C.SEQ = 
    (SELECT MAX(D.SEQ) FROM TABLE AS D 
    WHERE D.LVL = 3 AND D.SEQ <= COUNTER.SEQ) 
+0

Oui, les niveaux sont fixés - réel La tâche utilise les niveaux 3 à 9. Merci pour cette idée, je ne l'avais pas considéré. Toujours en espérant qu'il puisse y avoir un moyen plus facile de traiter sans les jointures de soi. – Patrick

0

Pour utiliser la clause connect, vous avez réellement besoin de liens entre les lignes. Donc, il devrait y avoir une colonne qui aurait un lien vers la ligne précédente qui a la valeur voulue. Avec ces champs, il est assez difficile de faire un choix car vous devez avoir 2 sous-sélections pour chaque ligne pour vérifier les autres niveaux.

Je voudrais utiliser la procédure pl/sql si cela est approprié.

declare 
    cursor c_cur is 
    select * from test order by seq asc; 

lvl1 test.descr%type := null; 
lvl2 test.descr%type := null; 
lvl3 test.descr%type := null; 

begin 

for rec in c_cur loop 
    if rec.lvl = 1 then 
     lvl1 := rec.descr; 
    elsif rec.lvl = 2 then 
     lvl2 := rec.descr; 
    elsif rec.lvl = 3 then 
     lvl3 := rec.descr; 
    end if; 
    dbms_output.put_line(rec.seq||','||nvl(lvl1, 'null')||','||nvl(lvl2, 'null')||','||nvl(lvl3, 'null')); 
end loop; 

end; 
/