Oracle: righe di ordinamento naturali su più livelli
Domanda
Uso di Oracle 10.2.0.
Ho una tabella che consiste in un numero di riga, un livello di rientro e testo. Devo scrivere una routine per ordinare in modo "naturale" il testo all'interno di un livello di rientro [che è figlio di un livello di rientro inferiore]. Ho una limitata esperienza con le routine analitiche e mi connetto da / precedente, ma da quello che ho letto qui e altrove, sembra che potrebbero essere utilizzate per aiutare la mia causa, ma non riesco a capire come.
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');
Questa:
SELECT ord, indent, LPAD(' ', indent, ' ') || text txt FROM t;
... rendimenti:
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 righe selezionate.
Nel caso che ho definito per te, ho bisogno della mia routine di impostare ORD 60 = 50 e ORD 50 = 60 [capovolgili] perché E è dopo D e prima di Z.
Lo stesso vale per ORD 80 e 90 [con 90 che portano 100 e 110 perché appartengono ad esso], 100 e 110. L'output finale dovrebbe essere:
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
Il risultato è che ogni livello di rientro è ordinato alfabeticamente, all'interno del livello di rientro, all'interno del livello di rientro principale.
Soluzione
Ecco cosa devo lavorare. Non ho idea di quanto possa essere efficiente su set più grandi. La parte difficile per me era identificare il "genitore" per una determinata riga basata esclusivamente sul rientro e sull'ordine originale.
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
Altri suggerimenti
Okay, eccoti. La parte difficile nella struttura dei dati è che il genitore non è (esplicitamente) conosciuto, quindi la prima parte della query non fa altro che identificare il genitore secondo le regole (per ogni nodo, ottiene tutti i nodi secondari a un livello di profondità, arrestandosi non appena l'identificazione è più piccola o uguale al nodo iniziale).
Il resto è facile, in pratica solo qualche ricorsione con connect by per ottenere gli oggetti nell'ordine desiderato (rinumerandoli 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;