Subject Re: [firebird-support] SQL puzzle: Order based on Prerequisite
Author Svein Erling Tysvær
>In a table called "Steps", I have the following fields:
>
>ID, B4Me, Dsc,
>
>-ID field contains a unique ID for each record
>-B4Me contains the ID of some other record in the table that MUST appear in a result set, BEFORE this record. B4Me may be null. This is called the "B4Me" order.
>-Records will be entered in random order, so Natural order cannot be relied upon as a substitute for B4Me
>
>I need an SQL statement that will return all records with a user supplied search word (such as "oil", or "glycol" or "micron") in the Dsc field, which could return
>a variable number of records, but what ever the number of records returned, the *order* must be such that each record is preceded by the record who's ID
>matches the B4Me value of a given record.&nbs p;
>
>Important Notes:
>
>- The SQL may return one or more records with a blank B4Me. In which case, Natural order can be used for those records, and they are all considered "First" or
>rather, "Before everything else". Once all the records with a blank B4Me are listed, the remaining must be in the B4Me order.
>
>- It is possible, though unlikely, that following the B4Me order will result in a circular reference. (Two or more records that reference each other, or the last
>record in a large result set, references the first (or any other in the result set) record in the B4Me field). In these cases, the SQL must either list all records until
>a circular reference is detected, then don't list the "offending" record, or display an error message, but it must not hang FB and get an "out of memory" error.
>
>Thanks in advance for any help you can provide.

The possibility of a circular reference, makes this a difficult task. SQL is great for working with sets of data, iteration and recursion are more difficult, but doable with CTEs or stored procedures, but I cannot think of how to exit a circular reference "gracefully" within one statement.

Hence, I'd recommend using a global temporary table:

CREATE GLOBAL TEMPORARY TABLE MyTmp
(ID INTEGER, OrderNo INTEGER)
ON COMMIT DELETE ROWS; --doesn't really matter whether you commit or delete rows in this particular case...

and then EXECUTE BLOCK or a STORED PROCEDURE;

EXECUTE BLOCK(MySearch varchar(50) = :MySearch) Returns(OutID Integer) as
declare variable ID integer;
declare variable ID2 integer;
declare variable OrderNo integer;
begin
delete from MyTmp; --start afresh every time you execute this block
OrderNo = 1000;
for select ID, B4Me
from "Steps"
where Dsc containing :MySearch
into :ID, :ID2 do
begin
while (:ID is not null) do
begin
if exists(select * from MyTmp where ID = :ID) then
begin --This is how to escape from a circular reference
id = null;
end
else
begin
INSERT INTO MyTmp(ID, OrderNo)
VALUES(:ID, :OrderNo);
OrderNo = OrderNo - 1;
ID = ID2;
if (ID is not null) then
begin
SELECT B4Me from "Steps" where ID = :ID into :ID2;
end
end
end
end
for select ID from MyTmp order by OrderNo into :OutID do
suspend;
end

I've interpreted "Before everything else" to mean "before any of its own children", not "before any children regardless of heritage" - or, to put it simple - you would appear before all your children, but not necessarily before my children.

Hopefully, this can serve as a template for how you can solve your situation, despite being more than one statement.
Set