On this page

Handle NULL and missing rows

Distinguish a NULL value from a query that returns no row. A nullable result field is a pointer; a missing one result is the driver’s no-rows error unless you declare it optional.

Read nullable columns and optional joined rows

The migration allows a gull’s nickname to be NULL. A left join also makes the sighting columns nullable when a gull has no sighting:

-- sgl.query(ListSightings, many)
SELECT gulls.ring, gulls.nickname, sightings.place
FROM gulls
LEFT JOIN sightings ON sightings.gull_id = gulls.id
ORDER BY gulls.id, sightings.seen_on;
-- sgl.query(SummariseGulls, many)
SELECT
    gulls.ring,
    COALESCE(gulls.nickname, 'ring ' || gulls.ring) AS known_as,
    COUNT(sightings.id) AS times_seen,
    MAX(sightings.seen_on) AS last_seen
FROM gulls
LEFT JOIN sightings ON sightings.gull_id = gulls.id
GROUP BY gulls.id
ORDER BY gulls.id;

COALESCE supplies a non-null display name; COUNT supplies zero, while the last-sighting aggregate remains nullable.

Check a field before dereferencing it

Run sgl generate and construct store.New(database). Read the rows and format nullable fields:

	rows, err := queries.ListSightings(ctx)
	if err != nil {
		return fmt.Errorf("listing sightings: %w", err)
	}
	fmt.Fprintln(output, "ring    nickname  place")
	for _, row := range rows {
		fmt.Fprintf(output, "%-7s %-9s %s\n", row.Ring, orNull(row.Nickname), orNull(row.Place))
	}

The caller’s helper handles nil explicitly:

func orNull(value *string) string {
	if value == nil {
		return "NULL"
	}
	return *value
}

For a missing whole row, either handle sql.ErrNoRows/pgx.ErrNoRows, or declare an optional single-row query. These are different from a row whose fields contain NULL.

Check missing values and aggregate results

From the Seagull repository root, run the nullability example:

cd examples/sqlite/004_nullability
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:

ring    nickname  place
GB1001  Steven    the pier
GB1001  Steven    the chip shop
GB1002  NULL      the harbour wall
GB1003  Gwen      NULL

ring    known as     times seen  last seen
GB1001  Steven       2           2026-05-03
GB1002  ring GB1002  1           2026-05-02
GB1003  Gwen         0           NULL

Keep value absence separate from row absence

Use nullable: false only when the query can never return NULL for that field. A NOT NULL declaration on the underlying table is insufficient on the nullable side of an outer join. For optional related objects, embed the joined result and check the nested pointer.

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