In Laravel's query builder, what do DB::table('orders')->get(), ->first() and ->value('total') return, including when no row matches?
answer
- plain rows, not Eloquent models
- Collection of stdClass objects
- first() adds limit 1
- null, not an exception
- firstOrFail throws RecordNotFoundException
basics
~10 sget() returns an Illuminate\Support\Collection of stdClass rows, empty when nothing matches; first() returns one stdClass or null; value('total') returns that column from the first row or null. Only firstOrFail() and sole() throw.
solid answer
~40 s`DB::table()` works with plain rows, not models. `get()` returns an `Illuminate\Support\Collection` of `stdClass` objects, and an empty collection when nothing matches, so check it with `isEmpty()`, never with `=== null`. `first()` adds `limit 1` and returns one `stdClass` or `null`. `value('total')` selects only that column from the first row and returns the bare value or `null`. None of these throw on a miss: `firstOrFail()` throws `RecordNotFoundException`, and `sole()` throws `RecordsNotFoundException` for zero rows or `MultipleRecordsFoundException` for more than one. Laravel's exception handler turns both not-found exceptions into a 404. Aggregates are terminal too: `count()` returns an int, and `sum()` returns 0 when no rows match.
code
php · 15 lines<?php
use Illuminate\Support\Facades\DB;
$paid = DB::table('orders')->where('status', 'paid')->get();
// Collection of stdClass; empty Collection when no paid orders
$latest = DB::table('orders')->latest()->first();
// stdClass or null
$total = DB::table('orders')->where('id', 42)->value('total');
// the column value or null
$order = DB::table('orders')->where('id', 42)->firstOrFail();
// throws RecordNotFoundException (rendered as a 404) on a missgo deeper
Recall the three return shapes: a Collection of stdClass from get(), one stdClass or null from first(), and a bare value or null from value().
Explain why a miss is null or an empty Collection rather than an exception, and when firstOrFail() or sole() is the better call.
Show when plain rows beat models, such as reports and bulk jobs, and what you give up: casts, hidden attributes and persistence.
Frame a team convention for when data access may bypass Eloquent, and how to keep response shapes from leaking raw columns.
## What `DB::table()` gives you `DB::table('orders')` asks the `DB` facade for the default database connection and returns an `Illuminate\Database\Query\Builder` for that table. The builder is a **fluent object**: every `where`, `join` or `orderBy` call adds a piece to an in-memory description of the SQL and returns the same builder, so nothing runs yet. SQL only reaches the database when you call a **terminal method** such as `get()`, `first()`, `value()`, `pluck()`, `count()` or `exists()`. What those methods return is the part interviewers probe, because the query builder does **not** return Eloquent models. Each row is a PHP `stdClass` object whose properties are the selected column names. There are no casts, no accessors, no relationships and no `save()` method. ## The terminal methods side by side | Call | Returns when rows match | Returns when nothing matches | |---|---|---| | `get()` | `Illuminate\Support\Collection` of `stdClass` | an **empty** Collection | | `first()` | one `stdClass` (runs with `limit 1`) | `null` | | `value('total')` | the column's value from the first row | `null` | | `find(5)` | the row whose `id` is 5 | `null` | | `pluck('total', 'id')` | Collection of values, optionally keyed | empty Collection | | `count()` | an `int` | `0` | | `sum('total')` | the database's sum | `0` | | `exists()` | `true` | `false` | A few details sit behind that table: - `first()` is literally `limit(1)->get()->first()`, so it is still a real query; calling it in a loop is one round trip per iteration. - `value()` selects **only** the named column, which makes it cheaper than `first()->total` on wide tables. - `find()` always compares against a column named `id`; a table with another primary key needs `where()` and `first()` instead. - `sum()` is written as the aggregate result `?: 0`, while `avg()`, `min()` and `max()` return `null` for an empty set. ## When nothing matches None of `get()`, `first()`, `value()` or `find()` throws on a miss. That is deliberate, but it moves the check into your code: 1. For `get()`, test `$rows->isEmpty()`. An empty Collection is an object, so `if ($rows)` is always true. 2. For `first()` and `find()`, test for `null` before reading a property. Reading `->total` on `null` raises a PHP warning, which Laravel's error handler converts into an `ErrorException`, so the request fails with a 500 instead of a clean 404. 3. When a missing row should end the request, call `firstOrFail()`. It throws `Illuminate\Database\RecordNotFoundException`, which the framework's exception handler converts into a 404 response. 4. When exactly one row must exist, call `sole()`. It throws `RecordsNotFoundException` for zero rows and `MultipleRecordsFoundException` for two or more, because it fetches with `limit 2` to detect duplicates. ## Rows are not models Because each result is a `stdClass`, several things Eloquent users take for granted are absent: - **No casts.** A `created_at` column comes back as the string the driver produced, not a Carbon instance, and a JSON column is a string you decode yourself. - **No hidden attributes.** Returning query-builder rows straight from a controller serialises every selected column, so select only what the response needs. - **No persistence.** Changing `$row->status` changes a PHP object and nothing else; writing back takes an explicit `DB::table('orders')->where('id', $row->id)->update([...])`. That trade-off is why teams reach for `DB::table()` in reports and bulk jobs, where hydrating thousands of models would cost memory and time for features the code never uses. ## Choosing the right terminal - Need a list to loop over or return: `get()`, selecting only the columns you use. - Need one row that may be missing: `first()` plus a `null` check, or `firstOrFail()` when a miss is a 404. - Need one scalar, such as a status or a total: `value()`. - Need an id-to-name map for a dropdown: `pluck('name', 'id')`. - Need a yes/no answer: `exists()`, which avoids fetching rows at all. ## What interviewers listen for A junior answer names the three return shapes. A stronger one adds that a miss is signalled by `null` or an empty Collection rather than an exception, names `firstOrFail()` and `sole()` as the throwing variants, and notes that the rows are plain objects with no model behaviour.
- How do firstOrFail() and sole() differ on the Laravel query builder?`firstOrFail()` runs `first()` and throws `RecordNotFoundException` only when no row matches; it happily returns the first of many. `sole()` fetches with `limit 2` and throws `RecordsNotFoundException` for zero rows or `MultipleRecordsFoundException` for two or more, so it also asserts uniqueness. Use `sole()` when a duplicate would mean corrupt data.
- Why might you pick value('total') over first()->total?`value('total')` selects only that column, so a wide row is never transferred, and it returns `null` cleanly on a miss. `first()->total` selects every column, and when `first()` returns `null` it reads a property on `null`: a PHP warning that Laravel's error handler turns into an `ErrorException`.
saying these in an interview costs you the question
- Says DB::table()->get() returns Eloquent models with casts
- Checks an empty get() result with === null
- Believes first() throws when no row matches
- Thinks value('total') returns the whole first row
- Expects changing a returned row object to update the database