add timescaledb extension migration and orders_daily_by_created continuous aggregate
P22.1 of Stage 22 materialization layer infrastructure. Adds explicit CREATE EXTENSION migration so clean environments can run `migrate` end-to-end without ops manual setup, then creates the first continuous aggregate orders_daily_by_created (day-bucketed, grouped by company/platform/store) with WITH NO DATA. Five composite indexes cover the high-frequency query patterns documented in docs/data_query.md. P22.2/P22.3 will add the by-paid view, refresh policy and historical backfill on top. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Db::statement('CREATE EXTENSION IF NOT EXISTS timescaledb');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 不主动 DROP EXTENSION:existing hypertables 依赖该扩展,drop 会破坏数据。
|
||||
// 完全清空数据库时手动执行 `DROP EXTENSION timescaledb CASCADE`。
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// 连续聚合视图:按订单创建日期日聚合(含未付订单)。
|
||||
// WITH NO DATA:视图创建时不立即物化,由 P22.3 的回填命令一次性填充历史数据。
|
||||
Db::statement(<<<'SQL'
|
||||
CREATE MATERIALIZED VIEW orders_daily_by_created
|
||||
WITH (timescaledb.continuous) AS
|
||||
SELECT
|
||||
time_bucket('1 day', created_date) AS day,
|
||||
company_id,
|
||||
platform_id,
|
||||
store_id,
|
||||
COUNT(*) AS total_orders,
|
||||
COUNT(*) FILTER (WHERE paid_date IS NOT NULL) AS paid_orders,
|
||||
COUNT(*) FILTER (WHERE paid_date IS NULL) AS unpaid_orders,
|
||||
SUM(total_amount) AS sum_total_amount,
|
||||
SUM(total_paid) AS sum_total_paid,
|
||||
SUM(total_received) AS sum_total_received,
|
||||
AVG(total_amount) AS avg_total_amount,
|
||||
AVG(total_paid) FILTER (WHERE paid_date IS NOT NULL) AS avg_paid_amount,
|
||||
SUM(freight_fee) AS sum_freight_fee,
|
||||
SUM(tax_fee) AS sum_tax_fee,
|
||||
SUM(commission_fee) AS sum_commission_fee,
|
||||
SUM(discount_fee) AS sum_discount_fee
|
||||
FROM orders
|
||||
GROUP BY day, company_id, platform_id, store_id
|
||||
WITH NO DATA;
|
||||
SQL);
|
||||
|
||||
// 复合索引:覆盖"最近 N 日按 X 维度"与"指定 X 全历史时间序列"两类高频查询。
|
||||
Db::statement('CREATE INDEX IF NOT EXISTS idx_orders_daily_by_created_day_company ON orders_daily_by_created (day DESC, company_id)');
|
||||
Db::statement('CREATE INDEX IF NOT EXISTS idx_orders_daily_by_created_day_platform ON orders_daily_by_created (day DESC, platform_id)');
|
||||
Db::statement('CREATE INDEX IF NOT EXISTS idx_orders_daily_by_created_day_store ON orders_daily_by_created (day DESC, store_id)');
|
||||
Db::statement('CREATE INDEX IF NOT EXISTS idx_orders_daily_by_created_company_day ON orders_daily_by_created (company_id, day DESC)');
|
||||
Db::statement('CREATE INDEX IF NOT EXISTS idx_orders_daily_by_created_store_day ON orders_daily_by_created (store_id, day DESC)');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// CASCADE 一并 drop 索引以及后续阶段附加的刷新策略。
|
||||
Db::statement('DROP MATERIALIZED VIEW IF EXISTS orders_daily_by_created CASCADE');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user