bavix/clickhouse-builder
PHP 7.1+ query builder for ClickHouse. Build and execute SELECT queries with a fluent API: select columns with aliases, closures for complex expressions or subqueries, and integrate with the-tinderbox/clickhouse-php-client for execution.
Install the package:
composer require the-tinderbox/clickhouse-builder
Initialize the client and builder (required for all queries):
use Tinderbox\Clickhouse\Client;
use Tinderbox\Clickhouse\Server;
use Tinderbox\Clickhouse\ServerProvider;
use Tinderbox\ClickhouseBuilder\Builder;
$server = new Server('127.0.0.1', '8123', 'default', 'user', 'pass');
$serverProvider = (new ServerProvider())->addServer($server);
$client = new Client($serverProvider);
$builder = new Builder($client);
First query (e.g., fetch data from a table):
$results = $builder->select('column1', 'column2')->from('table')->get();
select(), from(), where(), and get() for reads.join(), groupBy(), or orderBy().select(), from(), or where() for nested queries.Method chaining for readability:
$results = $builder
->select('user_id', 'name')
->from('users')
->where('active', true)
->orderBy('name', 'asc')
->limit(10)
->get();
Closure-based subqueries (for dynamic or reusable logic):
$builder->select(function ($column) {
$column->as('total_orders')
->query(function ($query) {
$query->select('count(*)')->from('orders')->where('user_id', '=', $userId);
});
});
Reusable query builders (e.g., for shared logic):
$userQuery = $builder->select('*')->from('users')->where('active', true);
$activeUsers = $userQuery->get();
$recentOrders = $builder->from('orders')
->whereIn('user_id', $userQuery)
->orderBy('created_at', 'desc')
->get();
Register the service provider (Laravel):
// config/app.php
'providers' => [
\Tinderbox\ClickhouseBuilder\Integrations\Laravel\ClickhouseServiceProvider::class,
],
Configure the connection (config/database.php):
'connections' => [
'clickhouse' => [
'driver' => 'clickhouse',
'host' => '127.0.0.1',
'port' => '8123',
'database' => 'default',
'username' => 'user',
'password' => 'pass',
],
],
Use the builder via DB facade:
$results = DB::connection('clickhouse')->query()
->select('*')
->from('users')
->get();
Upload and query local files:
$builder->addFile(new TempTable('numbersTable', 'numbers.tsv', ['number' => 'UInt64'], Format::TSV));
$results = $builder->select('*')->from('main_table')->whereIn('id', 'numbersTable')->get();
Bulk inserts from files:
$builder->table('logs')->insertFiles(['timestamp', 'message'], [
'logs_2023.tsv',
'logs_2024.tsv',
], Format::TSV);
Column alias syntax:
['column' => 'alias'] or 'column as alias' for clarity.'column as alias' works, but 'column as alias' with spaces may fail).Subquery closures:
select(), from(), or where() return valid queries.// ❌ Avoid: Missing `from()` in closure
$builder->where('column', function ($query) {
$query->select('value'); // Fails: No FROM clause
});
Temporary tables:
addFile() before using the table in whereIn() or join().Async queries:
asyncWithQuery() are returned as an array of results (indexed by query order).$results = $builder->asyncWithQuery(function ($query) {
$query->select('*')->from('table1');
})->asyncWithQuery(function ($query) {
$query->select('*')->from('table2');
})->get();
// $results[0] = table1 data, $results[1] = table2 data
Inspect raw SQL:
Use toSql() to debug queries before execution:
$sql = $builder->select('*')->from('users')->toSql();
Handle errors:
get() in a try-catch for ClickHouse exceptions:
try {
$results = $builder->select('*')->from('users')->get();
} catch (\Exception $e) {
Log::error($e->getMessage());
}
Check for deprecated methods:
Column class methods like sumIf() for production.Avoid SELECT *:
Explicitly list columns to reduce data transfer:
// ❌ Inefficient
$builder->select('*')->from('large_table');
// ✅ Better
$builder->select('id', 'name')->from('large_table');
Use LIMIT early:
Apply limit() before complex joins or aggregations to reduce intermediate result sets.
Leverage SAMPLE for analytics:
Use sample(0.1) for approximate queries on large datasets:
$builder->select('avg(value)')->from('metrics')->sample(0.1)->get();
Custom query builders:
Extend the Builder class to add domain-specific methods:
class UserBuilder extends Builder {
public function active() {
return $this->where('active', true);
}
}
Override SQL generation:
Extend the Builder class and override methods like compileSelect() or compileWhere() for custom syntax.
Add helper methods:
Create static methods for common queries (e.g., getActiveUsers()):
class QueryHelper {
public static function getActiveUsers(Builder $builder) {
return $builder->select('*')->from('users')->where('active', true);
}
}
How can I help you explore Laravel packages today?