I confirm your query determining the fully expanded list of A_ids works as
I explicitly tried it.

Now. I have actually simplified the problem before presenting it here in
the list. But I just tried to extrapolate the solution back  to the
original problem and failed miserably :-(

Yes, that is the problem with simplifying problems - one is liable to simplify-out some essential properties of the problem.

The original problem is not much more complicated, and for clarity, I will
get rid of the Table B step as I can understand that 2nd step perfectly
well.

So there is a 1 to 1 relationship between X objects and A objects.
A objects are complex and have elements. This elements then refer to either
another X (never the containing X) or refer to a B object.

Table X
id        A_id
=========
21          1
23          3
....

Table A
A_id
======
1
3
...

Table Aelements
object_id    index  X_id        B_id
=========================
1                0          NULL      5
1                1          23           NULL
1                2          NULL      7
3                0          NULL      2
3                1          NULL      3
...

Now extrapolating your recursive query, with the main input object is (X
id=21) I've tried:

WITH RECURSIVE AE(object_id, X_id, B_id) AS (
     SELECT object_id , X_id , B_id FROM Aelements WHERE object_id IN
(SELECT A_id FROM X WHERE id=21) AND X_id IS NULL
     UNION ALL
     SELECT Aelements.object_id , Aelements.X_id , Aelements.B_id FROM
Aelements, AE WHERE AE.object_id IN (SELECT A_id FROM X WHERE id IN (SELECT
X_id FROM Aelements WHERE object_id IN (SELECT A_id FROM X WHERE id=21) AND
X_id IS NOT NULL)))
SELECT * FROM AE;

But I think I'm failing to express the recursion in the above.
It doesn't work.

Basically in above, the resulting flattened Aelements list should be  1 3,
and therefore the corresponding B_id should be 5 7 2 3 , and finally the
last step from table B.

Right, this schema was made by a masochist. :)

I see David R. already had a go at a solution which seems perfect and it already probably works for you, I'm only going to elaborate a bit more for education purposes since you seem interested in learning about CTE and recursive queries.

Firstly, the table A is analogous to an Index and completely superfluous to our problem, so it can be ignored from the start. Aelements already contains the object id's we need, so we do not need table A.
I'm going to refer to Aelements simply as "E" from now on.

So if we start with Table X, the problem states (if my understanding is correct) that we need to check for the X.A_id of the given X.id, if there are any E.object_id's which has a reiterating reference back to table X in the E.X_id column and expand those corresponding X.A_id relational entries until we have exhaustively added every implicated A_id that is a 1st, 2nd, ... or n-th tier relation to the original X.id. This list we will then match to the B values later (which is easy and not part of this immediate problem, so I remove the B references completely from the CTE).

In the first (top) part of any CTE table is the root or origin query - which for our case is simply the original referenced X.id, so if we make a CTE with only the root part, it would be:
WITH AE(X_id, A_id) AS (
    SELECT X.id, X.A_id FROM X WHERE X.id = ?1
)
SELECT * FROM AE;

Note that we start from X_id and we look to expand for it the A_id values, and I know later we will need to refer back to the X_id values, so that is why I chose those field names in that order in the CTE.  The result of this query should be the single line from the X table that gives the correct root id. (Nice thing about CTEs is that you can at any point select from them, like we do here, to see what the results are so far).

Now we are ready to add the recursive part - We need for every X_id added to that CTE table, to also add any related A_id values which it points to AND which has a corresponding X_id in the E table that refers back to another X.id in the X table (i.e where the E.X_id column is not null). The recursive query part is simply the query that follows the root via a UNION *and* that refers back to the containing CTE. So to make AE a recursive CTE, we will add our id-expanding query after the UNION and refer (JOIN) back to the same AE cte  *as if it always contained the full list of X_id references we need* (this is the bit that feels a little like magic).

WITH AE(X_id, A_id) AS (
    SELECT X.id, X.A_id, NULL FROM X WHERE X.id = ?1
    UNION ALL
    SELECT E.X_id, X.A_id  FROM Aelements AS E JOIN X ON X.id = E.X_id WHERE E.object_id = AE.A_id AND E.X_id IS NOT NULL
)
SELECT * FROM AE;

As we scan the Aelements table for the object_id(s) that matches the A_id which our root query produced
[ ...FROM Aelements AS E ... WHERE E.object_id = AE.A_id... ],
any entries found with a valid X_id
[ ... WHERE ... E.X_id IS NOT NULL ]
is then matched back to the X table
[ ... JOIN X ON X.id = E.X_id ... ]
and that E.X_id and X.A_id is then appended to our AE cte table to await further processing.
[ ... UNION ALL SELECT E.X_id, X.A_id ... ]

It reminds me a little bit of how Lemmings used to build bridges. We traverse the query rows always adding more rows in front of us while processing the rows below our feet, so long as there exist more rows that match our criteria from those we process, we keep going, and if we reach the final one, self-terminate. :)

The final step is simple, use the output from this CTE (AE) to match the rows in Aelements finding all the B_ids which in turn matches the B table to find the B values.

WITH AE(X_id, A_id) AS (
    SELECT X.id, X.A_id, NULL FROM X WHERE X.id = ?1
    UNION ALL
    SELECT E.X_id, X.A_id  FROM Aelements AS E JOIN X ON X.id = E.X_id WHERE E.object_id = AE.A_id AND E.X_id IS NOT NULL
)
SELECT DISTINCT B.value
  FROM AE
  JOIN Aelements AS E ON E.object_id = AE.A_id
  JOIN B ON B.object_id = E.B_id
;

I've tried to explain some complex stuff in very simple terms here without writing an entire book, but probably failed miserably on all counts... All I can suggest beyond these explanations is to play with the CTE queries and inspect them at every step - I promise it becomes self-evident quickly how the machinations function on the inside, and when you grasp it once, it can never be un-grasped again.


Good luck and feel free to ask more if you still have questions,
Ryan

PS: again, I didn't test this on a real system, all sql is typed from my head, so mistakes have likely slipped in, but should work in theory.


_______________________________________________
sqlite-users mailing list
sqlite-users@mailinglists.sqlite.org
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to