staudenmeir/laravel-cte
Add Common Table Expression (CTE) support to Laravel’s query builder and Eloquent, including recursive and materialized CTEs. Works across MySQL, MariaDB, PostgreSQL, SQLite, SQL Server, Oracle, and SingleStore. Supports Laravel 5.5+.
Installation:
composer require staudenmeir/laravel-cte
For Laravel 5.5–5.7, add use \Staudenmeir\LaravelCte\Eloquent\QueriesExpressions; to your Eloquent models.
First Use Case: Replace a nested query with a CTE for cleaner, more efficient SQL:
$recentPosts = DB::table('posts')
->withExpression('recent', function ($query) {
$query->from('posts')
->where('created_at', '>', now()->subDays(7))
->select('id', 'title');
})
->join('recent', 'recent.id', '=', 'posts.category_id')
->get();
Simple CTEs:
Use withExpression() for non-recursive CTEs (e.g., subqueries for joins):
DB::table('orders')
->withExpression('high_value', function ($query) {
$query->from('orders')
->where('amount', '>', 1000)
->select('user_id');
})
->whereIn('user_id', DB::table('high_value'))
->get();
Recursive CTEs:
For hierarchical data (e.g., category trees), use withRecursiveExpression():
$categories = DB::table('categories')
->whereNull('parent_id')
->unionAll(
DB::table('categories')
->join('tree', 'tree.id', '=', 'categories.parent_id')
->select('categories.*')
)
->withRecursiveExpression('tree', $query)
->get();
Eloquent Integration: Extend Eloquent models for recursive relationships:
class Category extends Model
{
use \Staudenmeir\LaravelCte\Eloquent\QueriesExpressions;
}
$tree = Category::whereNull('parent_id')
->unionAll(
Category::select('categories.*')
->join('tree', 'tree.id', '=', 'categories.parent_id')
)
->withRecursiveExpression('tree', $query)
->get();
Write Operations:
Use CTEs in INSERT/UPDATE/DELETE:
DB::table('users')
->withExpression('active', DB::table('users')->where('status', 'active'))
->update(['last_login' => now()])
->whereIn('id', DB::table('active'));
Query Builder Extensions:
Override the query builder in AppServiceProvider for global CTE support:
public function boot()
{
DB::extend('cte', function ($app, $config) {
return new \Staudenmeir\LaravelCte\Query\Builder($app['db']->connection());
});
}
Custom Columns: Explicitly define columns for CTEs to avoid ambiguity:
DB::table('numbers')
->withRecursiveExpression('numbers', 'SELECT 1 AS number UNION ALL SELECT number + 1 FROM numbers WHERE number < 10', ['number'])
->get();
Cycle Detection: Prevent infinite recursion in hierarchical data (PostgreSQL/MariaDB 10.5.2+):
DB::table('tree')
->withRecursiveExpressionAndCycleDetection('tree', $query, 'id')
->get();
Materialized CTEs: Optimize performance for large datasets (PostgreSQL/SQLite):
DB::table('reports')
->withMaterializedExpression('temp_data', DB::table('logs')->where('status', 'completed'))
->get();
Database Compatibility:
UPDATE/DELETE with CTEs. Test thoroughly.new \Staudenmeir\LaravelCte\Query\OracleBuilder).Performance:
LIMIT clauses or cycle detection.Query Builder Overrides:
\Staudenmeir\LaravelCte\Query\Builder.Eloquent Traits:
Column Ambiguity:
SQL Dumping:
Use toSql() to inspect generated CTEs:
$sql = DB::table('posts')
->withExpression('recent', function ($query) { /* ... */ })
->toSql();
Cycle Detection:
If queries hang, check for infinite recursion or missing WHERE clauses in recursive CTEs.
PostgreSQL Errors:
Materialized CTEs may fail with ERROR: cannot use RETURNING to modify a materialized CTE. Use non-materialized CTEs instead.
Custom Grammar:
Extend \Staudenmeir\LaravelCte\Query\Grammars\Grammar for database-specific syntax (e.g., Oracle’s WITH clause).
Query Macros: Add reusable CTE patterns:
DB::macro('hierarchy', function ($model, $alias) {
return $model->whereNull('parent_id')
->unionAll($model->join('tree', 'tree.id', '=', $model->getTable().'.parent_id'));
});
Event Listeners: Log or validate CTE usage:
DB::listen(function ($query) {
if (str_contains($query->toSql(), 'WITH')) {
Log::debug('CTE used:', ['sql' => $query->toSql()]);
}
});
withExpression('user_activity', ...)) for maintainability.$query = DB::table('users')
->withExpression('active', 'SELECT id FROM users WHERE status = "active"')
->get();
staudenmeir/laravel-adjacency-list for ORM-level support.How can I help you explore Laravel packages today?