A Oracle - linhas de classificação naturais em vários níveis
Pergunta
Usando o Oracle 10.2.0.
Eu tenho uma tabela que consiste em um número de linha, um nível travessão e texto. Eu preciso escrever uma rotina para 'natural' tipo o texto dentro de um nível de avanço [que é filho de um nível de avanço inferior]. Eu tenho pouca experiência com rotinas de análise e conectar / antes, mas pelo que eu li aqui e em outros lugares, parece que eles poderiam ser colocados em uso para ajudar a minha causa, mas eu não consigo descobrir como.
CREATE TABLE t (ord NUMBER(5), indent NUMBER(3), text VARCHAR2(254));
INSERT INTO t (ord, indent, text) VALUES (10, 0, 'A');
INSERT INTO t (ord, indent, text) VALUES (20, 1, 'B');
INSERT INTO t (ord, indent, text) VALUES (30, 1, 'C');
INSERT INTO t (ord, indent, text) VALUES (40, 2, 'D');
INSERT INTO t (ord, indent, text) VALUES (50, 2, 'Z');
INSERT INTO t (ord, indent, text) VALUES (60, 2, 'E');
INSERT INTO t (ord, indent, text) VALUES (70, 1, 'F');
INSERT INTO t (ord, indent, text) VALUES (80, 2, 'H');
INSERT INTO t (ord, indent, text) VALUES (90, 2, 'G');
INSERT INTO t (ord, indent, text) VALUES (100, 3, 'J');
INSERT INTO t (ord, indent, text) VALUES (110, 3, 'H');
Este:
SELECT ord, indent, LPAD(' ', indent, ' ') || text txt FROM t;
... retornos:
ORD INDENT TXT
---------- ---------- ----------------------------------------------
10 0 A
20 1 B
30 1 C
40 2 D
50 2 Z
60 2 E
70 1 F
80 2 H
90 2 G
100 3 J
110 3 H
11 linhas selecionadas.
No caso eu definido por você, eu preciso da minha rotina para set ORD 60 = 50 e ORD 50 = 60 [lançá-los] por causa E é depois D e antes Z.
Mesmo com ORD 80 e 90 [com 90 trazendo 100 e 110 com ele porque pertencem a ele], 100 e 110. O resultado final deve ser:
ORD INDENT TXT
10 0 A
20 1 B
30 1 C
40 2 D
50 2 E
60 2 Z
70 1 F
80 2 G
90 3 H
100 3 J
110 2 H
O resultado é que cada nível de avanço é ordenada alfabeticamente, dentro do seu nível de avanço, dentro do nível travessão pai.
Solução
Aqui está o que eu tenho que trabalhar. Nenhuma idéia o quão eficiente pode ser em conjuntos maiores. A parte mais difícil para mim foi identificar o "pai" para uma determinada linha com base exclusivamente no travessão e ordem original.
WITH
a AS (
SELECT
t.*,
( SELECT MAX( ord )
FROM t t2
WHERE t2.ord < t.ord AND t2.indent = t.indent-1
) AS parent_ord
FROM
t
)
SELECT
ROWNUM*10 AS ord,
indent,
rpad( ' ', LEVEL-1, ' ' ) || text
FROM
a
CONNECT BY
PRIOR ord = parent_ord
START WITH
parent_ord IS NULL
ORDER SIBLINGS BY
text
Outras dicas
Ok, aqui vai. A parte mais difícil em sua estrutura de dados é que o pai não é (explicitamente) conhecida, de modo que a primeira parte da consulta não faz nada, mas identificar o pai de acordo com as regras (para cada nó, ele recebe todos os subnós um nível de profundidade, parando assim que o identation é menor ou igual ao nó de início).
O resto é fácil, basicamente, apenas alguns recursão com conexão por para obter os itens na ordem que você quer que eles (renumerando-los dinamicamente).
WITH OrdWithParentInfo AS
(SELECT ID,
INDENT,
TEXT,
MIN(ParentID) ParentID
FROM (SELECT O.*,
CASE
WHEN (CONNECT_BY_ROOT ID = ID) THEN
NULL
ELSE
CONNECT_BY_ROOT ID
END ParentID
FROM (SELECT ROWNUM ID,
INDENT,
TEXT
FROM T
ORDER BY ORD) O
WHERE (INDENT = CONNECT_BY_ROOT INDENT + 1)
OR (CONNECT_BY_ROOT ID = ID)
CONNECT BY ((ID = PRIOR ID + 1) AND (INDENT > CONNECT_BY_ROOT INDENT)))
GROUP BY ID,
INDENT,
TEXT)
SELECT ROWNUM * 10 ORD, O.INDENT, O.TEXT
FROM OrdWithParentInfo O
START WITH O.ParentID IS NULL
CONNECT BY O.ParentID = PRIOR ID
ORDER SIBLINGS BY O.Text;