What is Querying?
Querying is how you ask your database for specific information. Instead of getting all records, you can filter, sort, and limit results to get exactly what you need.Real-world analogy: Querying is like using filters on a shopping website. Instead of seeing all products, you filter by category, price range, brand, etc., to find exactly what you want.
Basic Queries
Getting All Records
Finding Single Records
The Query Builder
The query builder lets you chain methods to build complex queries:How chaining works: Each method returns a query builder object, so you can keep adding more conditions. Think of it like building a sentence: “Get users WHERE active is true AND age is greater than 18, ORDER BY name, LIMIT to 10.”
Where Clauses
Basic Where Conditions
Available Operators
OR Conditions
Where In / Not In
Null Checks
Between Values
Date Queries
whereDate, whereDay, whereMonth, whereYear, and whereTime all work across MySQL, PostgreSQL, and SQLite.JSON Queries
For databases that support JSON (PostgreSQL, MySQL):Ordering Results
Limiting and Pagination
Basic Limiting
Built-in Pagination
Aggregates
Get summary information about your data:Grouping and Having
Joins
Combine data from multiple tables:When to use joins vs relationships: Use joins when you need specific data from related tables in a single query. Use relationships (covered in the next section) when you want to work with related models as objects.
Raw Queries
Sometimes you need to write custom SQL:Advanced Subqueries
Subquery in addSelect
Add a computed column from a subquery without a join:
Order by a subquery
Sort results by a value computed from another table:Pending Attributes on Scopes
When you use a scoped query to create records,withPendingAttributes() lets the scope automatically set default column values on any model created through it:
create() on the scoped query:
Column Expressions with F()
Use F() to reference a column’s current value in an update — no raw SQL, no race conditions, no need to fetch the record first:
.plus(n), .minus(n), .times(n), .divide(n).
F() generates a safe parameterized expression — it never interpolates values directly into SQL.Plain Objects with values()
get() always returns model instances. Use values() when you only need the raw data and want to skip model hydration — faster for read-heavy endpoints:
Strict Fetching and Debugging
sole() — exactly one or throw
sole() is like firstOrFail() but also throws if more than one record matches. Use it when your query should always return exactly one row:
tap() — debug without breaking the chain
tap() runs a callback for logging or inspection without affecting the query:
Conditional Queries
Build queries based on conditions:Exists Queries
Relation existence (recommended)
UsewhereHas / doesntHave to filter by whether a named relationship exists. These build a WHERE EXISTS subquery automatically.
The related model must be registered in
ModelRegistry (call static { this.register(); } in its class body) so the ORM can resolve its table name at query-build time.has(relation, operator?, count?) is the simple form — filter by relation existence with an optional count. Use whereHas when you need to add constraints on the related records themselves.
Raw subquery existence
Query Scopes
Reusable query logic (defined in your model):Performance Tips
1. Use Specific Selects
2. Use Limits
3. Use Indexes
Make sure your database has indexes on columns you query frequently:4. Use Eager Loading for Relationships
Common Query Patterns
Search Functionality
Dashboard Statistics
Filtering with Multiple Options
Next Steps
Learn Relationships
Connect different models together
Database Migrations
Set up and modify your database structure
