Querying & Scopes
The Queryable module in CQL provides a powerful, type-safe, and chainable interface for constructing SQL queries using idiomatic Crystal code. It enables you to filter, sort, join, and aggregate records with a fluent API, abstracting away raw SQL while retaining full expressiveness and performance.
This guide covers how to use the query interface provided by CQL Active Record models and repositories, including all major query methods and best practices.
Basic Usage
You can build queries directly on your model or repository. Most query methods return a chainable query object, allowing you to compose complex queries step by step.
Example: Simple Filtering
# Find all users named "Alice"
alice_users = User.where(name: "Alice").all
# Find the first user with a specific email
user = User.where(email: "alice@example.com").first
# Using query builder directly
users = User.query.where { age > 18 }.order(:name).all(User)Example: Using a Repository
user_repo = UserRepository.new
# Find all users with a given domain
gmail_users = user_repo.query.where { email.like("%@gmail.com") }.all(User)Chainable Query Methods
CQL provides a rich set of chainable query methods that allow you to build complex queries in a type-safe and expressive way. Each method returns a query object that can be further chained with additional methods.
where
whereThe where method is used to filter records based on conditions. It supports both simple key-value pairs and block-based conditions.
where_like
where_likePattern matching using SQL LIKE operator.
order
orderSort records using the order method. You can specify multiple columns and sort directions.
reorder
reorderReplace existing order clauses with new ones.
reverse_order
reverse_orderReverse the current ordering direction.
limit and offset
limit and offsetControl the number of records returned and paginate through results.
select
selectSpecify which columns to retrieve from the database.
group and group_by
group and group_byGroup records by one or more columns, often used with aggregate functions.
having
havingAdd conditions to grouped records, typically used with group_by.
join, left, right
join, left, rightPerform SQL joins between tables. CQL supports various join types and provides a type-safe way to specify join conditions.
distinct
distinctRemove duplicate records from the result set.
unscope
unscopeRemove specific scopes from the query.
Finding Records
find and find!
find and find!Find a record by its primary key.
find_by and find_by!
find_by and find_by!Find a record by attributes.
Terminal Operations
Terminal operations execute the query and return the results. These methods are typically used at the end of a query chain to fetch the data from the database.
all
allReturns all records matching the query as an array.
first, first!, last, last!
first, first!, last, last!Returns the first or last record matching the query.
count
countReturns the number of records matching the query.
size
sizeAlias for count - returns the number of records.
exists?
exists?Checks if any records match the query.
any? and many?
any? and many?Check existence and quantity.
empty?
empty?Checks if no records match the query.
pluck
pluckReturns an array of values for the specified columns.
pick
pickExtract a single column value from the first matching record.
ids
idsGet array of primary key values.
Aggregate Functions
Calculate statistics using built-in aggregate functions.
Batch Processing
Process large datasets efficiently using batch methods.
find_each
find_eachProcess records one at a time in batches.
find_in_batches
find_in_batchesProcess records in batches.
Query Inspection and Debugging
to_sql
to_sqlGet the generated SQL for debugging.
none
noneCreate a query that returns no results (useful for conditional building).
Advanced Query Composition
CQL's query interface allows you to build complex queries by combining multiple query methods. This section demonstrates common patterns and best practices for composing sophisticated queries.
Advanced Filtering
Combine multiple conditions using logical operators and complex expressions.
Joins with Complex Conditions
Combine joins with filtering and ordering.
Aggregations with Grouping
Combine grouping with aggregations and having clauses.
Subqueries and Complex Filtering
Use subqueries for advanced filtering and data retrieval.
Query Scopes and Reusable Methods
Define reusable query scopes for common query patterns.
Conditional Query Building
Build queries dynamically based on conditions.
Performance Optimization
Efficient Querying
Query Analysis and Debugging
Best Practices Summary
Query Construction
Use meaningful method names and extract complex queries into scopes
Chain methods logically - filters first, then joins, then ordering
Use block syntax for complex conditions to improve readability
Leverage automatic relationship detection for simpler joins
Performance
Always use
.selectto limit columns when you don't need all dataUse
.exists?instead of.count > 0for existence checksUse batch processing (
.find_each,.find_in_batches) for large datasetsUse
.pluckfor extracting single column values efficiently
Maintainability
Define reusable scopes for common query patterns
Use conditional query building for dynamic filters
Extract complex queries into well-named methods
Document complex business logic in query methods
Security
Always use parameterized queries (CQL handles this automatically)
Validate user input before using in dynamic query construction
Use
.noneas a safe default for access controlBe careful with user-provided LIKE patterns
Testing
Test query scopes in isolation
Verify that complex queries return expected results
Test edge cases (empty results, large datasets)
Use
.to_sqlto verify generated SQL in tests
Related Guides
For more information on related query topics:
Complex Queries - Advanced query patterns and techniques
CRUD Operations - Basic create, read, update, delete operations
Relations - Understanding model relationships and associations
Scopes - Reusable query methods and named scopes
Transactions - Managing database transactions
Validations - Data validation and integrity
Last updated
Was this helpful?