Complex Queries

CQL provides a powerful, type-safe query builder that allows you to compose sophisticated SQL queries using idiomatic Crystal code. This guide demonstrates how to build complex queries using Model.query and the CQL::Query interface.


Join Types and Strategies

Implicit vs Explicit Joins

CQL supports both implicit and explicit join syntax for different use cases:

# Implicit JOIN - CQL automatically detects relationships based on foreign keys
users_with_posts = User.query.joins(:posts).all(User)
# Generates: SELECT * FROM users INNER JOIN posts ON users.id = posts.user_id

# Explicit JOIN - Full control over join conditions
users_with_posts = User.query.join(Post) { users.id == posts.user_id }
                             .all(User)

Foreign Key Requirements for Implicit Joins

Important: Implicit joins in CQL require foreign key relationships to be explicitly defined in your schema. CQL uses these foreign key definitions to automatically infer the correct JOIN conditions.

Schema Definition with Foreign Keys

For implicit joins to work, you must define foreign key constraints in your schema:

How CQL Uses Foreign Keys for Implicit Joins

When you use implicit joins, CQL:

  1. Looks up the foreign key definition in the schema

  2. Automatically determines the join condition based on the foreign key relationship

  3. Generates the appropriate SQL JOIN clause

Multi-Level Implicit Joins

With proper foreign key definitions, CQL can handle complex multi-level joins:

Without Foreign Keys - Explicit Joins Required

If foreign keys are not defined in the schema, you must use explicit joins:

Composite Foreign Keys

CQL supports composite foreign keys for complex relationships:

Foreign Key vs Column Reference

There's an important distinction between having a column that references another table and having a proper foreign key constraint:

Checking Schema Foreign Keys

You can inspect your schema's foreign key definitions:

Best Practices for Foreign Keys and Joins

  1. Always define foreign keys in your schema for relationships you'll query

  2. Use consistent naming for foreign key columns (e.g., user_id, post_id)

  3. Define foreign keys before creating tables in your schema

  4. Use explicit joins when you need custom join conditions beyond foreign key relationships

  5. Document complex relationships in your schema comments

This foreign key requirement ensures that CQL can provide type-safe, automatic join generation while maintaining clear relationships in your database schema.

Inner Joins (Default)

Inner joins return only records that have matching records in both tables:

Left Joins

Left joins return all records from the left table and matching records from the right table:

Right Joins

Right joins return all records from the right table and matching records from the left table:

Self Joins

Join a table to itself for hierarchical or comparative queries:

Cross Joins

Generate Cartesian product of two tables (use with caution):


Advanced Filtering

Combine multiple conditions, logical operators, and expressions:


Ordering and Sorting

Basic Ordering

Advanced Ordering


Pagination and Limiting

Basic Pagination

Cursor-Based Pagination


Grouping, Aggregation, and Having

Use grouping and aggregate functions for reporting and analytics:


DISTINCT Queries

Remove duplicate results from your queries:


Subqueries

Use subqueries for advanced filtering and data retrieval:


Raw SQL Integration

Inspecting Generated SQL

Executing Raw SQL


N+1 Query Prevention

One of the most common performance issues in ORMs is the N+1 query problem, where loading a collection of records results in 1 query for the main records plus N additional queries for each associated record. CQL provides several mechanisms to prevent this issue.

Understanding the N+1 Problem

CQL's JOIN-Based Approach

CQL automatically uses efficient JOIN-based queries instead of separate queries for each association:

Has Many Associations

Many-to-Many Associations

Lazy Loading with Caching

CQL implements intelligent lazy loading that prevents duplicate queries:

Efficient Association Operations

CQL provides methods to perform common operations without loading full records:

Count Without Loading

Existence Checks Without Loading

ID-Only Operations

Query Optimization Strategies

Batch Operations

Strategic Use of Joins

Performance Best Practices

Advanced N+1 Prevention Patterns

Manual Preloading

Caching Association Counts

Monitoring and Debugging

Query Analysis

Performance Testing


Query Optimization Tips

Query Analysis


Advanced Query Patterns

Conditional Queries

Complex Aggregations

Window Functions (if supported by your database)


Best Practices Summary

Query Construction

  • Use table aliases for clarity in multi-join queries

  • Use block syntax for complex conditions to improve readability

  • Leverage automatic relationship detection for simpler joins

  • Use explicit joins when you need custom conditions

  • Define foreign keys in your schema to enable implicit joins and automatic relationship detection

Performance

  • Always use .select to limit columns and improve performance

  • Use .limit and .offset for pagination

  • Prefer EXISTS over IN for subqueries with large result sets

  • Use proper indexing strategy for WHERE clause columns

  • Leverage association count and existence methods to avoid N+1 queries

  • Use JOIN-based aggregation instead of iterating over associations

  • Monitor query patterns and optimize based on actual usage patterns

Maintainability

  • Use meaningful variable names and method extraction for complex queries

  • Use .to_sql to inspect and verify generated SQL

  • Test query performance with realistic data volumes

  • 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 proper escaping for LIKE patterns


For more information on related query topics:

Last updated

Was this helpful?