skip to content

In Laravel's query builder, what do DB::table('orders')->get(), ->first() and ->value('total') return, including when no row matches?

level: juniorimportance: must knowfreq 62%

answer

  1. plain rows, not Eloquent models
  2. Collection of stdClass objects
  3. first() adds limit 1
  4. null, not an exception
  5. firstOrFail throws RecordNotFoundException

basics

~10 s

get() 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
<?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 miss

go deeper

for a junior

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().

for a middle

Explain why a miss is null or an empty Collection rather than an exception, and when firstOrFail() or sole() is the better call.

for a senior

Show when plain rows beat models, such as reports and bulk jobs, and what you give up: casts, hidden attributes and persistence.

for a principal

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