Recursion

Common Table Expressions allow us to, effectively, create our own temporary tables for the duration of a query - they’re largely a convenience to help us make more readable SQL. Using the WITH RECURSIVE modifier, however, it’s possible for us to create recursive queries. This is enormously advantageous for working with tree and graph-structured data - imagine retrieving all of the relations of a graph node to a given depth, for example.

Find the upward recommendation chain for member ID 27

Find the upward recommendation chain for member ID 27: that is, the member who recommended them, and the member who recommended that member, and so on. Return member ID, first name, and surname. Order by descending member id.

  1. WITH RECURSIVE recommenders(recommender) as (
  2. SELECT recommendedby FROM members WHERE memid = 27
  3. UNION ALL
  4. SELECT mems.recommendedby
  5. FROM recommenders recs
  6. INNER JOIN members AS mems ON mems.memid = recs.recommender
  7. )
  8. SELECT recs.recommender, mems.firstname, mems.surname
  9. FROM recommenders AS recs
  10. INNER JOIN members AS mems ON recs.recommender = mems.memid
  11. ORDER By memid DESC;
  1. # Base-case of recursive CTE. Get member recommender where memid=27.
  2. base = (Member
  3. .select(Member.recommendedby)
  4. .where(Member.memid == 27)
  5. .cte('recommenders', recursive=True, columns=('recommender',)))
  6. # Recursive term of CTE. Get recommender of previous recommender.
  7. MA = Member.alias()
  8. recursive = (MA
  9. .select(MA.recommendedby)
  10. .join(base, on=(MA.memid == base.c.recommender)))
  11. # Combine the base-case with the recursive term.
  12. cte = base.union_all(recursive)
  13. # Select from the recursive CTE, joining on member to get name info.
  14. query = (cte
  15. .select_from(cte.c.recommender, Member.firstname, Member.surname)
  16. .join(Member, on=(cte.c.recommender == Member.memid))
  17. .order_by(Member.memid.desc()))