since/clickhouse

一个类 ThinkPHP 风格的 ClickHouse 链式查询组件

1.0.0 2025-09-23 08:37 UTC

This package is auto-updated.

Last update: 2025-09-23 08:44:14 UTC


README

一个类 ThinkPHP 风格的 ClickHouse 链式查询组件,让 PHP 开发者能像操作 MySQL 一样轻松操作 ClickHouse。

特性

  • ✅ 类 ThinkPHP 链式查询语法
  • ✅ 支持 ClickHouse 原生 SQL 与 Builder 混合使用
  • ✅ 支持 HTTP 协议(默认)与 TCP 协议(可选)
  • ✅ 支持 WHERE、ORDER BY、GROUP BY、LIMIT、HAVING、SELECT、FIELD、JOIN(ASOF / INNER / LEFT)等常用语法
  • ✅ 支持数据插入(INSERT)、批量插入、替代插入(ReplacingMergeTree)
  • ✅ 支持预处理防注入(参数绑定)
  • ✅ 支持 Laravel、ThinkPHP、Lumen 等主流框架集成
  • ✅ 提供假事务机制(用于API兼容性)
  • ✅ 提供详细文档与使用示例

安装

通过 Composer 安装:

composer require since/clickhouse

配置

基本配置

use ClickHouse\DB;

// 设置配置信息
DB::setConfig([
    'default' => 'default',
    'connections' => [
        'default' => [
            'protocol' => 'http',
            'host' => 'localhost',
            'port' => 8123,
            'database' => 'default',
            'username' => 'default',
            'password' => '',
            'timeout' => 30,
            'debug' => false,
        ],
    ],
]);

Laravel 集成

在 Laravel 中,发布配置文件:

php artisan vendor:publish --tag=clickhouse-config

然后在 .env 文件中配置:

CLICKHOUSE_HOST=localhost
CLICKHOUSE_PORT=8123
CLICKHOUSE_DATABASE=default
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=

ThinkPHP 集成

在 ThinkPHP 中,创建 config/clickhouse.php 配置文件:

return [
    'default' => 'default',
    'connections' => [
        'default' => [
            'protocol' => 'http',
            'host' => '127.0.0.1',
            'port' => 8123,
            'database' => 'default',
            'username' => 'default',
            'password' => '',
            'timeout' => 30,
            'debug' => false,
        ],
    ],
];

然后在服务提供者中注册:

use ClickHouse\DB;

// 在应用启动时加载配置
DB::setConfig(config('clickhouse'));

基本用法

查询数据

use ClickHouse\DB;

// 基本查询
$data = DB::name('user_log')
    ->where('uid', '>', 100)
    ->where('date', '>=', '2025-09-01')
    ->whereIn('type', [1, 2, 3])
    ->field('uid, sum(score) as total')
    ->group('uid')
    ->having('total', '>', 100)
    ->order('total desc')
    ->limit(10)
    ->select();

// 获取单条记录
$user = DB::name('users')
    ->where('id', 1)
    ->first();

// 获取单个值
$name = DB::name('users')
    ->where('id', 1)
    ->value('name');


// 聚合查询
$count = DB::name('user_log')->count();
$max = DB::name('user_log')->max('score');
$min = DB::name('user_log')->min('score');
$avg = DB::name('user_log')->avg('score');
$sum = DB::name('user_log')->sum('score');

条件查询

// 基本条件
DB::name('user_log')->where('uid', 123);
DB::name('user_log')->where('age', '>', 18);

// 组合条件
DB::name('user_log')
    ->where('uid', 123)
    ->where('age', '>', 18)
    ->where('status', 'in', [1, 2]);

// OR 条件
DB::name('user_log')
    ->where('uid', 123)
    ->orWhere('uid', 456);

// 原生条件
DB::name('user_log')
    ->whereRaw("toDate(time) = '2025-09-23'");

// IN 条件
DB::name('user_log')
    ->whereIn('uid', [1, 2, 3]);

// BETWEEN 条件
DB::name('user_log')
    ->whereBetween('age', 18, 30);

// NULL 条件
DB::name('user_log')
    ->whereNull('deleted_at');

// NOT IN 条件
DB::name('user_log')
    ->whereNotIn('uid', [1, 2, 3]);

// NOT BETWEEN 条件
DB::name('user_log')
    ->whereNotBetween('age', 18, 30);

插入数据

// 插入单条数据
DB::name('user_log')
    ->insert([
        'uid' => 123,
        'score' => 99,
        'date' => '2025-09-23',
        'extra' => json_encode(['ip' => '1.1.1.1'])
    ]);

// 批量插入
DB::name('user_log')
    ->insertAll([
        ['uid' => 1, 'score' => 99, 'date' => '2025-09-23'],
        ['uid' => 2, 'score' => 88, 'date' => '2025-09-23'],
        ['uid' => 3, 'score' => 77, 'date' => '2025-09-23'],
    ]);

// 替代插入(适用于 ReplacingMergeTree)
DB::name('user_log')
    ->replace([
        'uid' => 123,
        'name' => 'Tom',
        'date' => '2025-09-23',
    ]);

更新数据

// 基本更新
DB::name('user_log')
    ->where('uid', '=', 123)
    ->update(['score' => 100]);

// 字段增加值
DB::name('user_log')
    ->where('uid', '=', 123)
    ->setInc('score', 10);

// 字段减少值
DB::name('user_log')
    ->where('uid', '=', 123)
    ->setDec('score', 5);

// 批量增加多个字段的值
DB::name('user_log')
    ->where('uid', '=', 123)
    ->setInc(['score' => 10, 'views' => 1]);

// 批量减少多个字段的值
DB::name('user_log')
    ->where('uid', '=', 123)
    ->setDec(['score' => 5, 'stock' => 1]);

JOIN 查询

// 基本 JOIN
$data = DB::name('user_log l')
    ->join('users u', 'l.uid = u.id')
    ->field('u.name, sum(l.score) as total')
    ->group('u.name')
    ->select();

// LEFT JOIN
$data = DB::name('user_log l')
    ->leftJoin('users u', 'l.uid = u.id')
    ->select();

// INNER JOIN
$data = DB::name('user_log l')
    ->innerJoin('users u', 'l.uid = u.id')
    ->select();

// ASOF JOIN(ClickHouse 特有)
$data = DB::name('user_log l')
    ->asofJoin('users u', 'l.uid = u.id')
    ->select();

子查询

// 子查询
$sub = DB::name('user_active')->field('uid')->where('active', 1);

$data = DB::name('user_log')
    ->whereIn('uid', $sub)
    ->select();

// WITH 子句
$data = DB::name('user_log')
    ->with('active_users', DB::name('user_active')->where('active', 1))
    ->join('active_users au', 'user_log.uid = au.uid')
    ->select();

原生 SQL

// 执行原生查询
$data = DB::query("SELECT * FROM user_log WHERE uid = ? AND date = ?", [123, '2025-09-23']);

// 执行原生命令
$result = DB::execute("INSERT INTO user_log (uid, score) VALUES (?, ?)", [123, 99]);

// 在构建器中使用原生表达式
$data = DB::name('user_log')
    ->where('date', '=', DB::raw("toDate('2025-09-23')"))
    ->select();

辅助函数

// 获取查询构建器实例
$query = clickhouse();

// 指定表名
$query = clickhouse_table('user_log');

// 执行原生查询
$data = clickhouse_query("SELECT * FROM user_log WHERE uid = ?", [123]);

// 执行原生命令
$result = clickhouse_execute("INSERT INTO user_log (uid) VALUES (?)”, [123]);

// 创建原生表达式
$expr = clickhouse_raw("toDate('2025-09-23')");

假事务机制

ClickHouse 的事务功能是实验性的,本组件提供假事务机制用于 API 兼容性:

// 假事务示例
DB::begin();
try {
    // 执行数据操作
    DB::name('user_log')->insert(['uid' => 123, 'score' => 99]);
    
    // 提交事务
    DB::commit();
} catch (Exception $e) {
    // 回滚事务(注意:这不会实际撤销已执行的操作)
    DB::rollback();
}

// 嵌套事务
DB::begin();
try {
    // 外层操作
    DB::name('table1')->insert(['name' => 'test']);
    
    DB::begin();
    try {
        // 内层操作
        DB::name('table2')->insert(['value' => 100]);
        DB::commit(); // 内层提交
    } catch (Exception $e) {
        DB::rollback(); // 内层回滚
    }
    
    DB::commit(); // 外层提交
} catch (Exception $e) {
    DB::rollback(); // 外层回滚
}

ClickHouse 特有功能

PREWHERE 优化

// 使用 PREWHERE 优化查询性能
$data = DB::name('user_log')
    ->prewhere('date', '=', '2025-09-23')
    ->where('uid', '>', 100)
    ->select();

批量插入优化

// 大批量数据插入
$rows = [];
for ($i = 0; $i < 10000; $i++) {
    $rows[] = [
        'uid' => $i,
        'score' => rand(1, 100),
        'date' => '2025-09-23',
    ];
}

DB::name('user_log')->insertAll($rows);

高级用法

多连接管理

// 使用指定连接
$data = DB::connection('analytics')
    ->name('user_log')
    ->select();

// 或者
$data = DB::name('user_log', 'analytics')->select();

调试模式

// 开启调试模式
$query = DB::connection()->debug(true);

// 获取最后执行的 SQL
$sql = DB::connection()->getLastSql();

注意事项

  1. ClickHouse 不支持 UPDATE 和 DELETE 操作(除非使用特殊引擎或 ALTER TABLE)
  2. 使用 TCP 协议需要安装 ext-clickhouse 扩展或 Swoole
  3. 大批量插入时建议使用 insertAll 方法,性能更好
  4. 本组件提供的事务机制是假事务,主要用于 API 兼容性,不提供真正的事务保证
  5. 在假事务中,调用 ROLLBACK() 不会实际撤销已执行的操作
  6. ClickHouse 中单个 INSERT/UPDATE/DELETE 操作本身具有原子性,但不支持跨多个操作的事务一致性

许可证

MIT