开发者

Ordering SQL results based on predecessor

I have the following table in my database:

create table (
    name 开发者_Python百科       varchar(25),
    value       int,
    predecessor varchar(25)
);

with sample data:

name           | value | predecessor
---------------+-------+---------------
buyingredients | 10    | null
eat            |  3    | cook
cook           | 12    | mixingredients
mixingredients |  5    | buyingredients

I want an SQL query that selects name and value, ordered so that the item with the predecessor null is first, and then the row with who's predecessor is equal to that row's name is next and so on (i.e. buyingredients, mixingredients, cook, eat).

The ordering is strictly linear - the behaviour if two rows have the same value of predecessor is undefined.

I'm struggling to find an SQL query that will produce the ordering I want. I don't really know where to start.

I'm using an Informix database, though any variant of SQL would be a useful starting point.

updated to reflect the fact the ordering is not alphabetical


In Transact-SQL with common table expressions:

WITH LinkedList (name, value, predecessor, ordering)
AS
(
    SELECT a.name, a.value, a.predecessor, 0
    FROM   YourTable a
    WHERE  a.predecessor IS NULL
    UNION ALL
    SELECT b.name, b.value, b.predecessor, c.ordering + 1
    FROM   YourTable b
    INNER JOIN LinkedList c
    ON     b.predecessor = c.name
)
SELECT d.name, d.value
FROM   LinkedList d
ORDER BY d.ordering, d.name

I don't know whether Informix has this construct, but what you are asking is in essence a recursive query, which common table expressions give you in Transact-SQL.


Informix does not support a WITH clause, much less WITH RECURSIVE. That makes those approaches inoperable. There isn't a particularly clean way of doing it. It is not entirely clear what should happen if you have two or three lists, nor is it entirely clear whether you are dealing with simple lists (the example is a list) or a more general tree structure.

However, you could create a temporary table and populate that with a loop in a stored procedure, and then select from the temporary table in an appropriate order. This is fiddly rather than dreadfully difficult. You do a breadth-first search of the table. I note that you did not give your table a name - so it is hereinafter called 'anonymous'.

CREATE TEMP TABLE Flattened
(
    Hierarchy   SERIAL,
    Level       INTEGER,
    Name        VARCHAR(25),
    Value       INTEGER,
    Predecessor VARCHAR(25)
);

CREATE TEMP TABLE Intermediate
(
    Hierarchy   SERIAL,
    Level       INTEGER,
    Name        VARCHAR(25),
    Value       INTEGER,
    Predecessor VARCHAR(25)
);

INSERT INTO Flattened(Hierarchy, Level, Name, Value, Predecessor)
   SELECT 0, 0, name, value, predecessor
     FROM Anonymous
    WHERE Predecessor IS NULL;

WHILE ...any rows were inserted into table...
    INSERT INTO Intermediate(Hierarchy, Level, Name, Value, Predecessor)
        SELECT F.Hierarchy, F.Level + 1, A.Name, A.Value, A.Predecessor
          FROM Flattened AS F, Anonymous AS A
         WHERE F.Name = A.Predecessor
           AND F.Level = (SELECT MAX(Level) FROM Flattened);
    INSERT INTO Flattened SELECT * FROM Intermediate;
    DELETE FROM Intermediate;
END WHILE

DROP TABLE Intermediate;

Now you can use:

SELECT Name, Value, Predecessor
  FROM Flattened
 ORDER BY Hierarchy, Level, Name;

The only residual tricky bit is working out how many rows were inserted. In a stored procedure, it is probably simplest to do:

SELECT COUNT(*) INTO n_count FROM Flattened;

and

LET o_count = n_count - 1;
WHILE o_count != n_count
    ...as above - two INSERT operations and a DELETE operation
    LET o_count = n_count;
    SELECT COUNT(*) INTO n_count FROM Flattened;
END WHILE;

Putting it all together, this works for me (in a logged database).

BEGIN;

CREATE TABLE Anonymous
(
     Name        VARCHAR(25),
     Value       INTEGER,
     Predecessor VARCHAR(25)
);

INSERT INTO Anonymous VALUES("buyingredients", 10, NULL);
INSERT INTO Anonymous VALUES("eat", 3, "cook");
INSERT INTO Anonymous VALUES("cook", 12, "mixingredients");
INSERT INTO Anonymous VALUES("mixingredients", 5, "buyingredients");

CREATE PROCEDURE Flatten_Anonymous()

    DEFINE old_count INTEGER;
    DEFINE new_count INTEGER;

    CREATE TEMP TABLE Flattened
    (
        Hierarchy   SERIAL,
        Level       INTEGER,
        Name        VARCHAR(25),
        Value       INTEGER,
        Predecessor VARCHAR(25)
    );

    CREATE TEMP TABLE Intermediate
    (
        Hierarchy   SERIAL,
        Level       INTEGER,
        Name        VARCHAR(25),
        Value       INTEGER,
        Predecessor VARCHAR(25)
    );

    INSERT INTO Flattened(Hierarchy, Level, Name, Value, Predecessor)
       SELECT 0, 0, name, value, predecessor
         FROM Anonymous
        WHERE Predecessor IS NULL;

    SELECT COUNT(*) INTO new_count FROM Flattened;
    LET old_count = new_count - 1;
    WHILE old_count != new_count
        INSERT INTO Intermediate(Hierarchy, Level, Name, Value, Predecessor)
            SELECT F.Hierarchy, F.Level + 1, A.Name, A.Value, A.Predecessor
              FROM Flattened AS F, Anonymous AS A
             WHERE F.Name = A.Predecessor
               AND F.Level = (SELECT MAX(Level) FROM Flattened);
        INSERT INTO Flattened SELECT * FROM Intermediate;
        DELETE FROM Intermediate;
        LET old_count = new_count;
        SELECT COUNT(*) INTO new_count FROM Flattened;
    END WHILE

    DROP TABLE Intermediate;

END PROCEDURE;

EXECUTE PROCEDURE Flatten_Anonymous();

SELECT Name, Value, Predecessor
  FROM Flattened
 ORDER BY Hierarchy, Level, Name;

DROP TABLE Flattened;

ROLLBACK;

The output is:

buyingredients     10
mixingredients      5  buyingredients
cook               12  mixingredients
eat                 3  cook

Test platform: Informix 11.70.FC2 running on MacOS X 10.6.7.

Not formally tested with multiple independent hierarchies, or with tree-structured hierarchies instead of simple lists.


I'm using an Informix database, though any variant of SQL would be a useful starting point.

I know nothing about Informix so here is a version for SQL Server. @T is table variable I use as a test table. To chain the rows I use a recursive CTE.

declare @T table
(
  name varchar(25),
  value int,
  predecessor varchar(25)
)  

insert into @T
select 'buyingredients', 10, null union all
select 'cook',           12, 'mixingredients' union all
select 'mixingredients',  5, 'buyingredients' union all
select 'eat',             3, 'cook'

;with cte as 
(
  select T.name,
         T.value,
         T.predecessor,
         1 as sortorder
  from @T as T
  where T.predecessor is null
  union all
  select T.name,
         T.value,
         T.predecessor,
         C.sortorder+1 as sortorder
  from @T as T
    inner join cte as C
      on T.predecessor = C.name
)
select C.name,
       C.value,
       C.predecessor
from cte as C
order by C.sortorder

You can try it out here: https://data.stackexchange.com/stackoverflow/q/102534/recursive-cte-to-build-a-chain

Edit

I still don't know anything about Informix but perhaps you can do something using Node DataBlade Module


I think the way to do what you want is to construct a temporary table with a "depth" field for every item, then use that depth field to order your results.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜