Columns That Can Be NULL
Every column so far has been NOT NULL, which kept a mismatch hidden: SQL has NULL and Go's string has no equivalent. Scan a NULL into a *string destination and you get a run-time error, not a zero value:
sql: Scan error on column index 2, name "note":
converting NULL to string is unsupported
The compiler cannot warn you, because Scan takes ...any. You find out when a row that happens to be NULL comes back — usually in production, because your test fixtures all set the column.
There are two good answers, and the choice is about meaning.
sql.NullString when absence is information
var note sql.NullString
err := row.Scan(&t.ID, &t.Title, ¬e)
if note.Valid {
t.Note = note.String
}
Valid is false when the column was NULL. Use this when the caller genuinely needs to tell "no note" from "an empty note" — the same distinction that makes a *string necessary in a PATCH request body.
The package has one per type: sql.NullInt64, sql.NullBool, sql.NullTime. They are ordinary structs, so they scan into anything.
COALESCE when absence is just a default
If your domain has no use for the distinction, do not drag it into Go. Let the database collapse it:
SELECT id, title, COALESCE(note, '') FROM tasks WHERE id = ?
Now the column is never NULL and a plain string destination is correct. This is the better answer more often than people expect: a Task.Note that is "" when unset is simpler than one where every reader must check Valid, and simpler is what you want unless the difference means something.
A pointer is the third option
*string also works — nil for NULL. It is honest about absence, but it puts a nil check on every read, and it makes the zero value of your struct contain a nil pointer. Prefer sql.NullString at the scan boundary and convert to whatever your domain wants.
The rule underneath
Decide what NULL means in your domain before you decide how to scan it. If it means "not set yet, and that matters", carry it. If it means "nothing, treat as empty", flatten it in SQL and keep your Go types simple.
Try it
Read tasks whose note column is nullable, and return a plain string — using the database to collapse the distinction your domain doesn't need.
Practice
1. Read a Nullable Column
IntermediateThe
taskstable now has a nullablenotecolumn. ImplementGetTask(db *sql.DB, id int64) (Task, error).- Populate
ID,TitleandNote. Noteis a plainstring: a NULL note must come back as"", not an error.- A missing row returns
ErrNotFound, as before.
Either approach works — a
sql.NullStringdestination, or letting the database collapse the NULL. The tests only care that a NULL note becomes""and that a real note survives intact.package main import ( "database/sql" "errors" "fmt" _ "modernc.org/sqlite" ) var ErrNotFound = errors.New("task not found") type Task struct { ID int64 Title string Note string // "" when the column is NULL } // GetTask returns the task with the given id, or ErrNotFound. func GetTask(db *sql.DB, id int64) (Task, error) { // TODO: implement return Task{}, fmt.Errorf("not implemented") } func main() {}Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- Populate