views:

248

answers:

2

Using Oracle 10.2.0.

I have a table that consists of a line number, an indent level, and text. I need to write a routine to 'natural' sort the text within an indent level [that is a child of a lower indent level]. I have limited experience with analytic routines and connect by/prior, but from what I've read here and elsewhere, it seems like they could be put to use to help my cause, but I can't figure out how.

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

This:

SELECT ord, indent, LPAD(' ', indent, ' ') || text txt FROM t;

...returns:

   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 rows selected.

In the case I've defined for you, I need my routine to set ORD 60 = 50 and ORD 50 = 60 [flip them] because E is after D and before Z.
Same with ORD 80 and 90 [with 90 bringing 100 and 110 with it because they belong to it], 100 and 110. The final output should be:

   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

The result is that each indent level is sorted alphabetically, within its indent level, within the parent indent level.

+1  A: 

Here's what I got to work. No idea how efficient it might be on larger sets. The hard part for me was identifying the "parent" for a given row based solely on indent and original order.

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
kurosch
Hehe... almost posted at the same time. I must admit that your way of finding the parents is way smaller, I had a hard time with that...
Lucero
Thanks! I couldn't wrap my head around finding the parent! Fantastic solution!
A: 

Okay, here you go. The hard part in your data structure is that the parent is not (explicitly) known, so that the first part of the query does nothing but identify the parent according to the rules (for each node, it gets all subnodes one level deep, stopping as soon as the identation is smaller or equal to the start node).

The rest is easy, basically just some recursion with connect by to get the items in the order you want them (renumbering them dynamically).

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;
Lucero
great minds think alike :-)
kurosch
I guess so (even though I don't see myself as "great mind" ;) ). Was fun anyways.
Lucero