On this page

Query through CTEs, views and functions

Compose a query from intermediate projections while keeping its results typed. This guide traces a family through a recursive SQLite CTE; reusable view and SQL-function variants are linked below.

Name the recursive projection

In a configured SQLite project, use a nullable parent_id to link each gull to its parent. The migration declares that relationship. Put the recursive query in your query file:

-- sgl.query(FamilyTree, many)
WITH RECURSIVE family AS (
    SELECT id, name, parent_id, 0 AS generation
    FROM gulls
    WHERE id = ?
    UNION ALL
    SELECT gulls.id, gulls.name, gulls.parent_id, family.generation + 1
    FROM gulls
    JOIN family ON gulls.parent_id = family.id
)
SELECT family.name, family.generation, parents.name AS parent
FROM family
LEFT JOIN gulls AS parents ON parents.id = family.parent_id
ORDER BY family.generation, family.id;

The first arm establishes the CTE’s columns and types; the recursive arm follows each parent link. Give derived columns explicit names so callers can identify their generated fields.

Call the projection as one query

Run sgl generate, then call the method through store.New(database):

	family, err := queries.FamilyTree(ctx, 1)
	if err != nil {
		return fmt.Errorf("tracing Steven's family: %w", err)
	}
	fmt.Fprintln(output, "Steven's family:")
	for _, member := range family {
		fmt.Fprintf(output, "  generation %d  %-8s parent %s\n", member.Generation, member.Name, orNull(member.Parent))
	}

For a reusable summary, define a view in a migration and select from it, as the example’s BeachRecords query does. For a reusable operation with arguments, define a SQL function.

Check the recursive rows

From the Seagull repository root, run the projection example:

cd examples/sqlite/019_ctes_and_windows
go test .

The example test provides its local inputs. See its caller for the exact Go API and its test for assertions and failure handling.

The complete example produces:

Steven's family:
  generation 0  Steven   parent NULL
  generation 1  Gwen     parent Steven
  generation 1  Jago     parent Steven
  generation 2  Kerensa  parent Gwen
theft league:
  Gwithian      #1 Morwenna 0 of 0
  Porthmeor     #1 Steven   3 of 4
  Porthmeor     #2 Gwen     1 of 4
  Porthmeor     #3 Kerensa  0 of 4
  Porthminster  #1 Jago     2 of 3
  Porthminster  #2 Tristan  1 of 3
beach records:
  Gwithian      gulls 1, thefts 0, last theft NULL
  Porthmeor     gulls 3, thefts 4, last theft 2026-07-04
  Porthminster  gulls 2, thefts 3, last theft 2026-07-05

Choose the type of the recursive anchor

Catalogue-level Go type overrides follow direct columns through views, but not through CTEs or derived tables. Reapply a per-query sgl.column override when a derived output needs a custom representation. A recursive member retains its anchor’s types, so cast the anchor when it would otherwise be unknown.

See the type mapping reference for the complete contract. The example browser exposes other engine variants; use their actual SQL rather than changing only the dialect name.

Examples for this page