827 lines
35 KiB
SQL
827 lines
35 KiB
SQL
-- ============================================================
|
|
-- 连锁餐饮企业数字化运营平台 — 本体标准数据库
|
|
-- 创建时间: 2026-07-26
|
|
-- 说明: 维度主数据表 + 事实表 + 指标标准 + 枚举标准
|
|
-- ============================================================
|
|
|
|
-- ============================================================
|
|
-- 一、维度主数据表 (Dimension Tables)
|
|
-- ============================================================
|
|
|
|
-- 1. 门店主数据 (扩展现有 dim_store)
|
|
ALTER TABLE analytics.dim_store
|
|
ADD COLUMN IF NOT EXISTS brand TEXT DEFAULT '马兰拉面',
|
|
ADD COLUMN IF NOT EXISTS comparable BOOLEAN DEFAULT true,
|
|
ADD COLUMN IF NOT EXISTS management_quadrant TEXT,
|
|
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now();
|
|
|
|
-- 2. SKU主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_sku (
|
|
sku_code TEXT PRIMARY KEY,
|
|
standard_name TEXT NOT NULL,
|
|
pos_code TEXT,
|
|
meituan_code TEXT,
|
|
taobao_code TEXT,
|
|
jd_code TEXT,
|
|
historical_names TEXT[],
|
|
sku_type TEXT NOT NULL DEFAULT '单品', -- 单品/套餐主品/套餐组件/赠品/包装/服务项目
|
|
category_l1 TEXT,
|
|
category_l2 TEXT,
|
|
status TEXT NOT NULL DEFAULT '在售', -- 在售/停用/季节停/待定
|
|
effective_date DATE,
|
|
discontinued_date DATE,
|
|
brand TEXT DEFAULT '马兰拉面',
|
|
business_type TEXT, -- 兰州牛肉面/夜市烧烤/火锅等
|
|
applicable_stores TEXT[], -- NULL表示全部门店
|
|
tags TEXT[], -- 新品/季节品/区域品/战略品
|
|
abc_class TEXT, -- A-核心/B-成长/C-长尾
|
|
unit TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 3. 原料主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_material (
|
|
material_code TEXT PRIMARY KEY,
|
|
material_name TEXT NOT NULL,
|
|
specification TEXT,
|
|
major_category TEXT, -- 牛肉类/面粉类/蔬菜类/调味料类等
|
|
minor_category TEXT,
|
|
finance_category TEXT, -- 原材料/辅料/包装物/低值易耗品
|
|
base_unit TEXT NOT NULL, -- kg/个/包/瓶
|
|
conversion_ratio JSONB, -- {"kg": 1, "斤": 2}
|
|
supplier_code TEXT,
|
|
status TEXT DEFAULT '启用',
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 4. 会员主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_member (
|
|
member_id TEXT PRIMARY KEY,
|
|
phone_hash TEXT, -- 脱敏手机号
|
|
register_channel TEXT, -- 堂食/美团/淘宝/京东/小程序
|
|
register_store TEXT,
|
|
register_date DATE,
|
|
member_level TEXT DEFAULT '普通', -- 普通/银卡/金卡/钻石
|
|
total_orders INTEGER DEFAULT 0,
|
|
total_revenue NUMERIC DEFAULT 0,
|
|
last_order_date DATE,
|
|
status TEXT DEFAULT '活跃', -- 活跃/沉睡/流失
|
|
tags TEXT[],
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 5. 员工主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_employee (
|
|
employee_id TEXT PRIMARY KEY,
|
|
employee_name TEXT NOT NULL,
|
|
position TEXT, -- 店长/副店长/厨师长/厨师/服务员/收银员/配送员
|
|
store_code TEXT,
|
|
hire_date DATE,
|
|
leave_date DATE,
|
|
status TEXT DEFAULT '在职', -- 在职/离职/休假
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 6. 渠道主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_channel (
|
|
channel_code TEXT PRIMARY KEY,
|
|
channel_name TEXT NOT NULL,
|
|
channel_group TEXT NOT NULL, -- 堂食/外卖/支付
|
|
channel_type TEXT, -- 堂食/美团到店/美团外卖/支付宝/微信/现金/银联/抖音/挂账/京东到家/淘宝外卖
|
|
platform TEXT, -- 美团/淘宝/京东/抖音/自有
|
|
commission_rate NUMERIC, -- 平台佣金率%
|
|
is_delivery BOOLEAN DEFAULT false,
|
|
sort_order INTEGER DEFAULT 0,
|
|
status TEXT DEFAULT '启用',
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 7. 活动主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_promotion (
|
|
promotion_id TEXT PRIMARY KEY,
|
|
promotion_name TEXT NOT NULL,
|
|
channel TEXT, -- 美团/淘宝/京东/堂食/全渠道
|
|
start_date DATE,
|
|
end_date DATE,
|
|
applicable_stores TEXT[], -- NULL表示全部
|
|
applicable_skus TEXT[], -- NULL表示全部
|
|
platform_bear NUMERIC DEFAULT 0, -- 平台承担金额
|
|
company_bear NUMERIC DEFAULT 0, -- 公司承担金额
|
|
store_bear NUMERIC DEFAULT 0, -- 门店承担金额
|
|
target_audience TEXT, -- 新客/老客/全客
|
|
budget NUMERIC,
|
|
status TEXT DEFAULT '进行中', -- 计划中/进行中/已结束/已暂停
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 8. 日历主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_calendar (
|
|
date_value DATE PRIMARY KEY,
|
|
year INTEGER NOT NULL,
|
|
month INTEGER NOT NULL,
|
|
day INTEGER NOT NULL,
|
|
weekday_no INTEGER NOT NULL, -- 1=周一, 7=周日
|
|
weekday_name TEXT NOT NULL,
|
|
is_weekend BOOLEAN NOT NULL,
|
|
is_holiday BOOLEAN DEFAULT false,
|
|
holiday_name TEXT,
|
|
is_operating_day BOOLEAN DEFAULT true,
|
|
weather TEXT, -- 晴/阴/雨/雪/雾
|
|
temperature NUMERIC,
|
|
special_event TEXT, -- 商圈活动/门店周边事件
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 9. 供应商主数据
|
|
CREATE TABLE IF NOT EXISTS analytics.dim_supplier (
|
|
supplier_code TEXT PRIMARY KEY,
|
|
supplier_name TEXT NOT NULL,
|
|
supplier_type TEXT, -- 原料/包装/设备/服务
|
|
contact_person TEXT,
|
|
contact_phone TEXT,
|
|
region TEXT,
|
|
status TEXT DEFAULT '合作中',
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- ============================================================
|
|
-- 二、事实表 (Fact Tables)
|
|
-- ============================================================
|
|
|
|
-- 1. 账单事实表 (物化视图已存在 analytics.bill_fact,此处建规范事实表)
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_bill (
|
|
record_id BIGINT PRIMARY KEY,
|
|
source_file TEXT,
|
|
source_row INTEGER,
|
|
store_code TEXT NOT NULL,
|
|
store_name TEXT,
|
|
meal_period TEXT, -- 午市/晚市/夜宵/外卖
|
|
bill_no TEXT,
|
|
business_area TEXT,
|
|
table_type TEXT,
|
|
table_no TEXT,
|
|
business_date DATE GENERATED ALWAYS AS ((closed_at AT TIME ZONE 'Asia/Shanghai')::date) STORED,
|
|
consumption NUMERIC DEFAULT 0,
|
|
discount_total NUMERIC DEFAULT 0,
|
|
received_total NUMERIC DEFAULT 0,
|
|
guest_count NUMERIC DEFAULT 0,
|
|
table_count NUMERIC DEFAULT 0,
|
|
theoretical_cost NUMERIC DEFAULT 0,
|
|
theoretical_profit NUMERIC DEFAULT 0,
|
|
theoretical_margin NUMERIC,
|
|
third_party_discount NUMERIC DEFAULT 0,
|
|
marketing_plan TEXT,
|
|
member_id TEXT,
|
|
member_level TEXT,
|
|
waiter TEXT,
|
|
cashier TEXT,
|
|
shift_name TEXT,
|
|
bill_status TEXT,
|
|
order_tag TEXT,
|
|
opened_at TIMESTAMPTZ,
|
|
closed_at TIMESTAMPTZ,
|
|
duration_minutes NUMERIC,
|
|
-- 支付渠道金额
|
|
cash_received NUMERIC DEFAULT 0,
|
|
alipay_received NUMERIC DEFAULT 0,
|
|
wechat_received NUMERIC DEFAULT 0,
|
|
meituan_received NUMERIC DEFAULT 0,
|
|
unionpay_received NUMERIC DEFAULT 0,
|
|
douyin_received NUMERIC DEFAULT 0,
|
|
credit_received NUMERIC DEFAULT 0,
|
|
jd_delivery_received NUMERIC DEFAULT 0,
|
|
meituan_delivery_received NUMERIC DEFAULT 0,
|
|
taobao_delivery_received NUMERIC DEFAULT 0,
|
|
-- 优惠明细
|
|
points_discount NUMERIC DEFAULT 0,
|
|
member_coupon_discount NUMERIC DEFAULT 0,
|
|
member_prestore_discount NUMERIC DEFAULT 0,
|
|
douyin_discount NUMERIC DEFAULT 0,
|
|
-- 平台佣金
|
|
meituan_delivery_commission NUMERIC DEFAULT 0,
|
|
taobao_delivery_commission NUMERIC DEFAULT 0,
|
|
jd_delivery_commission NUMERIC DEFAULT 0,
|
|
-- 异常标记
|
|
anomaly_reason TEXT,
|
|
is_anomaly BOOLEAN DEFAULT false,
|
|
is_zero_received BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 2. 账单明细事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_bill_item (
|
|
detail_id BIGINT PRIMARY KEY,
|
|
source_file TEXT,
|
|
source_row INTEGER,
|
|
store_code TEXT NOT NULL,
|
|
store_name TEXT,
|
|
bill_no TEXT NOT NULL,
|
|
business_type TEXT,
|
|
table_or_pickup_no TEXT,
|
|
guest_count NUMERIC,
|
|
opened_at TIMESTAMP,
|
|
closed_at TIMESTAMP,
|
|
ordered_at TIMESTAMP,
|
|
dish_name TEXT NOT NULL,
|
|
sku_code TEXT, -- 关联 dim_sku
|
|
category_l1 TEXT,
|
|
category_l2 TEXT,
|
|
item_type TEXT, -- 单品/套餐/赠品/加料/包装
|
|
production_department TEXT,
|
|
unit TEXT,
|
|
preparation_method TEXT,
|
|
sales_quantity NUMERIC DEFAULT 0,
|
|
unit_price NUMERIC DEFAULT 0,
|
|
gross_amount NUMERIC DEFAULT 0,
|
|
received_amount NUMERIC DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 3. 支付事实表 (一张账单一种支付方式一行)
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_payment (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
bill_record_id BIGINT NOT NULL, -- 关联 fact_bill.record_id
|
|
store_code TEXT NOT NULL,
|
|
bill_no TEXT,
|
|
business_date DATE,
|
|
channel_code TEXT NOT NULL, -- 关联 dim_channel
|
|
amount NUMERIC DEFAULT 0,
|
|
commission NUMERIC DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 4. 优惠使用事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_promotion_usage (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
bill_record_id BIGINT,
|
|
store_code TEXT NOT NULL,
|
|
bill_no TEXT,
|
|
business_date DATE,
|
|
promotion_id TEXT, -- 关联 dim_promotion
|
|
discount_amount NUMERIC DEFAULT 0,
|
|
platform_bear NUMERIC DEFAULT 0,
|
|
company_bear NUMERIC DEFAULT 0,
|
|
store_bear NUMERIC DEFAULT 0,
|
|
is_new_customer BOOLEAN,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 5. 平台订单损益表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_platform_order (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
platform_order_no TEXT NOT NULL,
|
|
platform TEXT NOT NULL, -- 美团/淘宝/京东
|
|
store_code TEXT NOT NULL,
|
|
business_date DATE NOT NULL,
|
|
gross_amount NUMERIC DEFAULT 0,
|
|
discount_amount NUMERIC DEFAULT 0,
|
|
received_amount NUMERIC DEFAULT 0,
|
|
commission NUMERIC DEFAULT 0,
|
|
delivery_fee NUMERIC DEFAULT 0,
|
|
refund_amount NUMERIC DEFAULT 0,
|
|
net_revenue NUMERIC DEFAULT 0,
|
|
theoretical_cost NUMERIC DEFAULT 0,
|
|
gross_profit NUMERIC DEFAULT 0,
|
|
is_cancelled BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 6. 库存快照事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_inventory_snapshot (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
store_code TEXT NOT NULL,
|
|
material_code TEXT NOT NULL,
|
|
snapshot_date DATE NOT NULL,
|
|
opening_quantity NUMERIC DEFAULT 0,
|
|
opening_amount NUMERIC DEFAULT 0,
|
|
purchase_quantity NUMERIC DEFAULT 0,
|
|
purchase_amount NUMERIC DEFAULT 0,
|
|
consumption_quantity NUMERIC DEFAULT 0,
|
|
consumption_amount NUMERIC DEFAULT 0,
|
|
ending_quantity NUMERIC DEFAULT 0,
|
|
ending_amount NUMERIC DEFAULT 0,
|
|
waste_quantity NUMERIC DEFAULT 0,
|
|
waste_amount NUMERIC DEFAULT 0,
|
|
transfer_in_quantity NUMERIC DEFAULT 0,
|
|
transfer_out_quantity NUMERIC DEFAULT 0,
|
|
inventory_days NUMERIC, -- 库存天数
|
|
is_negative BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
UNIQUE(store_code, material_code, snapshot_date)
|
|
);
|
|
|
|
-- 7. 采购收货事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_purchase_receipt (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
receipt_no TEXT,
|
|
store_code TEXT NOT NULL,
|
|
supplier_code TEXT,
|
|
material_code TEXT NOT NULL,
|
|
receipt_date DATE NOT NULL,
|
|
quantity NUMERIC DEFAULT 0,
|
|
unit_price_excl_tax NUMERIC DEFAULT 0,
|
|
unit_price_incl_tax NUMERIC DEFAULT 0,
|
|
tax_rate NUMERIC DEFAULT 0,
|
|
amount_excl_tax NUMERIC DEFAULT 0,
|
|
tax_amount NUMERIC DEFAULT 0,
|
|
amount_incl_tax NUMERIC DEFAULT 0,
|
|
return_quantity NUMERIC DEFAULT 0,
|
|
batch_no TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 8. 调拨事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_transfer (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
transfer_no TEXT,
|
|
from_store_code TEXT NOT NULL,
|
|
to_store_code TEXT NOT NULL,
|
|
material_code TEXT NOT NULL,
|
|
transfer_date DATE NOT NULL,
|
|
quantity NUMERIC DEFAULT 0,
|
|
amount NUMERIC DEFAULT 0,
|
|
operator TEXT,
|
|
approver TEXT,
|
|
reason TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 9. 报损事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_waste (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
waste_no TEXT,
|
|
store_code TEXT NOT NULL,
|
|
material_code TEXT NOT NULL,
|
|
waste_date DATE NOT NULL,
|
|
quantity NUMERIC DEFAULT 0,
|
|
amount NUMERIC DEFAULT 0,
|
|
reason TEXT, -- 过期/损坏/变质/操作失误/盘点差异
|
|
operator TEXT,
|
|
approver TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 10. BOM配方事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_recipe_bom (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
sku_code TEXT NOT NULL,
|
|
material_code TEXT NOT NULL,
|
|
bom_version TEXT NOT NULL DEFAULT 'v1',
|
|
standard_gross_quantity NUMERIC NOT NULL, -- 标准毛料量
|
|
standard_net_quantity NUMERIC NOT NULL, -- 标准净料量
|
|
unit TEXT NOT NULL,
|
|
yield_rate NUMERIC, -- 出成率%
|
|
waste_rate NUMERIC, -- 损耗率%
|
|
substitute_material TEXT, -- 替代原料
|
|
effective_date DATE NOT NULL DEFAULT '2026-01-01',
|
|
expiry_date DATE,
|
|
applicable_stores TEXT[],
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
UNIQUE(sku_code, material_code, bom_version)
|
|
);
|
|
|
|
-- 11. 员工排班事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_employee_shift (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
employee_id TEXT NOT NULL,
|
|
employee_name TEXT,
|
|
store_code TEXT NOT NULL,
|
|
shift_date DATE NOT NULL,
|
|
shift_name TEXT, -- 早班/中班/晚班/全天
|
|
planned_hours NUMERIC DEFAULT 0,
|
|
actual_hours NUMERIC DEFAULT 0,
|
|
overtime_hours NUMERIC DEFAULT 0,
|
|
wage NUMERIC DEFAULT 0,
|
|
overtime_pay NUMERIC DEFAULT 0,
|
|
meal_period TEXT, -- 午市/晚市/夜宵
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
UNIQUE(employee_id, store_code, shift_date, shift_name)
|
|
);
|
|
|
|
-- 12. 会员触达事实表
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_customer_contact (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
member_id TEXT NOT NULL,
|
|
contact_type TEXT NOT NULL, -- 短信/推送/券/电话/企微
|
|
contact_date DATE NOT NULL,
|
|
store_code TEXT,
|
|
campaign_id TEXT,
|
|
channel TEXT,
|
|
is_responded BOOLEAN DEFAULT false,
|
|
response_date DATE,
|
|
is_converted BOOLEAN DEFAULT false, -- 是否转化为消费
|
|
conversion_date DATE,
|
|
conversion_amount NUMERIC DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- 13. 门店任务事实表 (已存在 analytics.store_task,此处建规范版)
|
|
CREATE TABLE IF NOT EXISTS analytics.fact_store_task (
|
|
task_id INTEGER PRIMARY KEY,
|
|
plan_month DATE,
|
|
store_code TEXT NOT NULL,
|
|
priority TEXT NOT NULL, -- P0/P1/P2/P3
|
|
problem_indicator TEXT NOT NULL,
|
|
problem_module TEXT, -- 收入/成本/优惠/会员/库存/风险
|
|
current_value NUMERIC,
|
|
benchmark_value NUMERIC,
|
|
target_value NUMERIC,
|
|
problem_description TEXT,
|
|
action_required TEXT,
|
|
owner TEXT,
|
|
collaborators TEXT,
|
|
deadline DATE,
|
|
status TEXT DEFAULT '待启动',
|
|
process_evidence TEXT,
|
|
verification_indicator TEXT,
|
|
verification_result TEXT,
|
|
incomplete_reason TEXT,
|
|
next_step TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now(),
|
|
updated_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- ============================================================
|
|
-- 三、指标标准表 (Metric Dictionary)
|
|
-- ============================================================
|
|
|
|
-- 扩展现有 indicator_dictionary 表
|
|
ALTER TABLE analytics.indicator_dictionary
|
|
ADD COLUMN IF NOT EXISTS metric_code TEXT,
|
|
ADD COLUMN IF NOT EXISTS metric_category TEXT, -- 收入/成本/优惠/会员/库存/风险/人效
|
|
ADD COLUMN IF NOT EXISTS calculation_sql TEXT,
|
|
ADD COLUMN IF NOT EXISTS direction TEXT DEFAULT 'higher_better', -- higher_better/lower_better
|
|
ADD COLUMN IF NOT EXISTS unit TEXT, -- 元/%/个/天
|
|
ADD COLUMN IF NOT EXISTS granularity TEXT, -- 门店/区域/公司
|
|
ADD COLUMN IF NOT EXISTS version TEXT DEFAULT 'v1.0',
|
|
ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true;
|
|
|
|
-- ============================================================
|
|
-- 四、枚举标准表 (Enum Tables)
|
|
-- ============================================================
|
|
|
|
-- 1. 风险等级枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_risk_level (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
color TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 2. 任务优先级枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_priority (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
response_time TEXT,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 3. 任务状态枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_task_status (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 4. 验收结果枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_verification_result (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 5. SKU类型枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_sku_type (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 6. SKU状态枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_sku_status (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 7. 业态枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_business_type (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 8. 渠道组枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_channel_group (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 9. 指标方向枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_metric_direction (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL
|
|
);
|
|
|
|
-- 10. 会员状态枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_member_status (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 11. 报损原因枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_waste_reason (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- 12. 管理象限枚举
|
|
CREATE TABLE IF NOT EXISTS analytics.enum_management_quadrant (
|
|
code TEXT PRIMARY KEY,
|
|
label TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0
|
|
);
|
|
|
|
-- ============================================================
|
|
-- 五、创建索引
|
|
-- ============================================================
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_dim_sku_status ON analytics.dim_sku(status);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_sku_type ON analytics.dim_sku(sku_type);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_sku_category ON analytics.dim_sku(category_l1, category_l2);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_material_category ON analytics.dim_material(major_category);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_member_status ON analytics.dim_member(status);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_employee_store ON analytics.dim_employee(store_code);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_channel_group ON analytics.dim_channel(channel_group);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_promotion_status ON analytics.dim_promotion(status);
|
|
CREATE INDEX IF NOT EXISTS idx_dim_calendar_date ON analytics.dim_calendar(date_value);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_fact_bill_store_date ON analytics.fact_bill(store_code, business_date);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_bill_no ON analytics.fact_bill(bill_no);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_bill_anomaly ON analytics.fact_bill(is_anomaly) WHERE is_anomaly = true;
|
|
CREATE INDEX IF NOT EXISTS idx_fact_bill_item_store ON analytics.fact_bill_item(store_code);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_bill_item_bill_no ON analytics.fact_bill_item(bill_no);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_bill_item_dish ON analytics.fact_bill_item(dish_name);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_payment_bill ON analytics.fact_payment(bill_record_id);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_payment_channel ON analytics.fact_payment(channel_code);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_inventory_store_date ON analytics.fact_inventory_snapshot(store_code, snapshot_date);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_inventory_negative ON analytics.fact_inventory_snapshot(is_negative) WHERE is_negative = true;
|
|
CREATE INDEX IF NOT EXISTS idx_fact_purchase_store_date ON analytics.fact_purchase_receipt(store_code, receipt_date);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_waste_store_date ON analytics.fact_waste(store_code, waste_date);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_bom_sku ON analytics.fact_recipe_bom(sku_code);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_shift_store_date ON analytics.fact_employee_shift(store_code, shift_date);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_contact_member ON analytics.fact_customer_contact(member_id);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_task_store ON analytics.fact_store_task(store_code);
|
|
CREATE INDEX IF NOT EXISTS idx_fact_task_priority ON analytics.fact_store_task(priority);
|
|
|
|
-- ============================================================
|
|
-- 六、初始化枚举数据
|
|
-- ============================================================
|
|
|
|
INSERT INTO analytics.enum_risk_level (code, label, color, sort_order) VALUES
|
|
('红色', '红色', '#ef4444', 1),
|
|
('黄色', '黄色', '#eab308', 2),
|
|
('绿色', '绿色', '#22c55e', 3)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_priority (code, label, response_time, sort_order) VALUES
|
|
('P0', 'P0-紧急', '当日确认、7天内形成方案', 1),
|
|
('P1', 'P1-重点', '48小时确认、当月整改', 2),
|
|
('P2', 'P2-改善', '一周内纳入计划', 3),
|
|
('P3', 'P3-观察', '持续监控', 4)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_task_status (code, label, sort_order) VALUES
|
|
('待启动', '待启动', 1),
|
|
('进行中', '进行中', 2),
|
|
('待验收', '待验收', 3),
|
|
('已验收', '已验收', 4),
|
|
('已回滚', '已回滚', 5)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_verification_result (code, label, sort_order) VALUES
|
|
('达标', '达标', 1),
|
|
('改善中', '改善中', 2),
|
|
('未达标', '未达标', 3),
|
|
('待验收', '待验收', 4)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_sku_type (code, label, sort_order) VALUES
|
|
('单品', '单品', 1),
|
|
('套餐主品', '套餐主品', 2),
|
|
('套餐组件', '套餐组件', 3),
|
|
('赠品', '赠品', 4),
|
|
('包装', '包装', 5),
|
|
('服务项目', '服务项目', 6)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_sku_status (code, label, sort_order) VALUES
|
|
('在售', '在售', 1),
|
|
('停用', '停用', 2),
|
|
('季节停', '季节停', 3),
|
|
('待定', '待定', 4)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_business_type (code, label, sort_order) VALUES
|
|
('标准门店', '标准门店', 1),
|
|
('特殊业态', '特殊业态', 2),
|
|
('旗舰店', '旗舰店', 3),
|
|
('社区店', '社区店', 4),
|
|
('交通枢纽店', '交通枢纽店', 5)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_channel_group (code, label, sort_order) VALUES
|
|
('堂食', '堂食', 1),
|
|
('外卖', '外卖', 2),
|
|
('支付', '支付', 3)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_metric_direction (code, label) VALUES
|
|
('higher_better', '越高越好'),
|
|
('lower_better', '越低越好')
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_member_status (code, label, sort_order) VALUES
|
|
('活跃', '活跃', 1),
|
|
('沉睡', '沉睡(30天未消费)', 2),
|
|
('流失', '流失(90天未消费)', 3)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_waste_reason (code, label, sort_order) VALUES
|
|
('过期', '过期', 1),
|
|
('损坏', '损坏', 2),
|
|
('变质', '变质', 3),
|
|
('操作失误', '操作失误', 4),
|
|
('盘点差异', '盘点差异', 5)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
INSERT INTO analytics.enum_management_quadrant (code, label, sort_order) VALUES
|
|
('明星', '明星门店', 1),
|
|
('金牛', '金牛门店', 2),
|
|
('问题', '问题门店', 3),
|
|
('瘦狗', '瘦狗门店', 4)
|
|
ON CONFLICT (code) DO NOTHING;
|
|
|
|
-- ============================================================
|
|
-- 七、初始化渠道主数据
|
|
-- ============================================================
|
|
|
|
INSERT INTO analytics.dim_channel (channel_code, channel_name, channel_group, channel_type, platform, is_delivery, sort_order, status) VALUES
|
|
('cash', '现金', '支付', '现金', NULL, false, 1, '启用'),
|
|
('alipay', '支付宝', '支付', '支付宝', '支付宝', false, 2, '启用'),
|
|
('wechat', '微信', '支付', '微信', '微信', false, 3, '启用'),
|
|
('unionpay', '银联', '支付', '银联', '银联', false, 4, '启用'),
|
|
('credit', '挂账', '支付', '挂账', NULL, false, 5, '启用'),
|
|
('meituan', '美团到店', '堂食', '美团到店', '美团', false, 6, '启用'),
|
|
('meituan_delivery', '美团外卖', '外卖', '美团外卖', '美团', true, 7, '启用'),
|
|
('taobao_delivery', '淘宝外卖', '外卖', '淘宝外卖', '淘宝', true, 8, '启用'),
|
|
('jd_delivery', '京东到家', '外卖', '京东到家', '京东', true, 9, '启用'),
|
|
('douyin', '抖音', '堂食', '抖音', '抖音', false, 10, '启用')
|
|
ON CONFLICT (channel_code) DO NOTHING;
|
|
|
|
-- ============================================================
|
|
-- 八、初始化日历主数据 (2026年全年)
|
|
-- ============================================================
|
|
|
|
INSERT INTO analytics.dim_calendar (date_value, year, month, day, weekday_no, weekday_name, is_weekend, is_holiday, holiday_name, is_operating_day)
|
|
SELECT
|
|
d::date,
|
|
EXTRACT(year FROM d)::int,
|
|
EXTRACT(month FROM d)::int,
|
|
EXTRACT(day FROM d)::int,
|
|
EXTRACT(dow FROM d)::int,
|
|
CASE EXTRACT(dow FROM d)::int
|
|
WHEN 0 THEN '周日' WHEN 1 THEN '周一' WHEN 2 THEN '周二'
|
|
WHEN 3 THEN '周三' WHEN 4 THEN '周四' WHEN 5 THEN '周五'
|
|
WHEN 6 THEN '周六'
|
|
END,
|
|
EXTRACT(dow FROM d)::int IN (0, 6),
|
|
false, NULL, true
|
|
FROM generate_series('2026-01-01'::date, '2026-12-31'::date, '1 day'::interval) AS d
|
|
ON CONFLICT (date_value) DO NOTHING;
|
|
|
|
-- 标记2026年主要节假日
|
|
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '元旦' WHERE date_value = '2026-01-01';
|
|
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '春节' WHERE date_value BETWEEN '2026-02-17' AND '2026-02-23';
|
|
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '清明节' WHERE date_value BETWEEN '2026-04-04' AND '2026-04-06';
|
|
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '劳动节' WHERE date_value BETWEEN '2026-05-01' AND '2026-05-05';
|
|
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '端午节' WHERE date_value BETWEEN '2026-06-19' AND '2026-06-21';
|
|
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '中秋节' WHERE date_value BETWEEN '2026-09-25' AND '2026-09-27';
|
|
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '国庆节' WHERE date_value BETWEEN '2026-10-01' AND '2026-10-07';
|
|
|
|
-- ============================================================
|
|
-- 九、初始化指标标准 (扩展现有 indicator_dictionary)
|
|
-- ============================================================
|
|
|
|
UPDATE analytics.indicator_dictionary SET
|
|
metric_code = 'received_total',
|
|
metric_category = '收入',
|
|
direction = 'higher_better',
|
|
unit = '元',
|
|
granularity = '门店/区域/公司',
|
|
version = 'v1.0',
|
|
is_active = true
|
|
WHERE indicator_name = '实收' AND metric_code IS NULL;
|
|
|
|
UPDATE analytics.indicator_dictionary SET
|
|
metric_code = 'avg_bill_value',
|
|
metric_category = '收入',
|
|
direction = 'higher_better',
|
|
unit = '元',
|
|
granularity = '门店/区域/公司',
|
|
version = 'v1.0',
|
|
is_active = true
|
|
WHERE indicator_name = '客单价' AND metric_code IS NULL;
|
|
|
|
UPDATE analytics.indicator_dictionary SET
|
|
metric_code = 'discount_rate',
|
|
metric_category = '优惠',
|
|
direction = 'lower_better',
|
|
unit = '%',
|
|
granularity = '门店/区域/公司',
|
|
version = 'v1.0',
|
|
is_active = true
|
|
WHERE indicator_name = '优惠率' AND metric_code IS NULL;
|
|
|
|
-- 补充缺失的核心指标 (upsert: 已有的更新metric_code等字段,新的插入)
|
|
INSERT INTO analytics.indicator_dictionary (indicator_name, business_definition, formula, data_source, update_frequency, owner, scope, yellow_threshold, red_threshold, version_date, metric_code, metric_category, direction, unit, granularity, version, is_active)
|
|
SELECT * FROM (VALUES
|
|
('账单数', '有效账单总数', 'count(*)', 'bill_fact', '日', '运营部', '门店/区域/公司', NULL, NULL, '2026-04-01'::date, 'bill_count', '收入', 'higher_better', '个', '门店/区域/公司', 'v1.0', true),
|
|
('理论成本率', '理论成本占实收比例', 'sum(theoretical_cost)/sum(received_total)*100', 'bill_fact', '日', '财务部', '门店/区域/公司', 40, 45, '2026-04-01'::date, 'theoretical_cost_rate', '成本', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('实际成本率', '盘点倒挤成本占实收比例', 'sum(consumption_amount)/sum(received_total)*100', 'inventory_cost_records', '月', '财务部', '门店/区域/公司', 38, 42, '2026-04-01'::date, 'actual_cost_rate', '成本', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('成本差异率', '实际成本率与理论成本率差异', 'actual_cost_rate - theoretical_cost_rate', '计算指标', '月', '财务部', '门店/区域/公司', 3, 5, '2026-04-01'::date, 'cost_variance_rate', '成本', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('平台合并加权成本率', '三平台(美团+淘宝+京东)加权成本率', '(sum(commission)+sum(discount))/sum(received)*100', 'v_store_platform_economics', '月', '运营部', '门店/区域/公司', 25, 30, '2026-04-01'::date, 'platform_weighted_cost_rate', '优惠', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('会员识别率', '会员账单占总账单比例', 'count(member_id IS NOT NULL)/count(*)*100', 'bill_fact', '日', '会员部', '门店/区域/公司', 15, 10, '2026-04-01'::date, 'member_identify_rate', '会员', 'higher_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('30日复购率', '30天内二次消费会员占比', 'count(repeat_members)/count(identified_members)*100', 'v_store_repeat_summary_monthly', '月', '会员部', '门店/区域/公司', 30, 20, '2026-04-01'::date, 'repeat_rate_30d', '会员', 'higher_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('异常账单率', '异常账单数占总账单比例', 'count(is_anomaly)/count(*)*100', 'bill_fact', '日', '运营部', '门店/区域/公司', 0.1, 0.5, '2026-04-01'::date, 'anomaly_bill_rate', '风险', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('零实收账单数', '实收为0的账单数量', 'count(received_total=0)', 'bill_fact', '日', '运营部', '门店', 5, 10, '2026-04-01'::date, 'zero_received_count', '风险', 'lower_better', '个', '门店', 'v1.0', true),
|
|
('库存天数', '期末库存可消耗天数', 'ending_amount/(consumption_amount/30)', 'inventory_cost_records', '月', '供应链', '门店', 7, 14, '2026-04-01'::date, 'inventory_days', '库存', 'lower_better', '天', '门店', 'v1.0', true),
|
|
('负耗用货品数', '消耗量为负的货品数量', 'count(consumption_quantity<0)', 'inventory_cost_records', '月', '财务部', '门店', 0, 3, '2026-04-01'::date, 'negative_consumption_count', '库存', 'lower_better', '个', '门店', 'v1.0', true),
|
|
('理论毛利率', '理论毛利占消费额比例', 'sum(theoretical_profit)/sum(consumption)*100', 'bill_fact', '日', '财务部', '门店/区域/公司', 65, 60, '2026-04-01'::date, 'theoretical_margin_rate', '收入', 'higher_better', '%', '门店/区域/公司', 'v1.0', true),
|
|
('活动新客率', '活动期间新客占活动用户比例', 'count(is_new_customer)/count(*)*100', 'fact_promotion_usage', '月', '会员部', '门店/活动', 30, 15, '2026-04-01'::date, 'campaign_new_customer_rate', '优惠', 'higher_better', '%', '门店/活动', 'v1.0', true),
|
|
('SKU有效数', '在售且月实收>1000的SKU数量', 'count(sku_code WHERE status=在售 AND received_amount>1000)', 'fact_bill_item JOIN dim_sku', '月', '商品部', '公司/区域', 900, 700, '2026-04-01'::date, 'effective_sku_count', '商品', 'lower_better', '个', '公司/区域', 'v1.0', true),
|
|
('长尾SKU占比', 'C类SKU数占总SKU数比例', 'count(abc_class=C)/count(*)*100', 'dim_sku', '月', '商品部', '公司', 50, 60, '2026-04-01'::date, 'longtail_sku_ratio', '商品', 'lower_better', '%', '公司', 'v1.0', true)
|
|
) AS t(indicator_name, business_definition, formula, data_source, update_frequency, owner, scope, yellow_threshold, red_threshold, version_date, metric_code, metric_category, direction, unit, granularity, version, is_active)
|
|
ON CONFLICT (indicator_name) DO UPDATE SET
|
|
metric_code = EXCLUDED.metric_code,
|
|
metric_category = EXCLUDED.metric_category,
|
|
direction = EXCLUDED.direction,
|
|
unit = EXCLUDED.unit,
|
|
granularity = EXCLUDED.granularity,
|
|
version = EXCLUDED.version,
|
|
is_active = EXCLUDED.is_active,
|
|
business_definition = EXCLUDED.business_definition,
|
|
formula = EXCLUDED.formula,
|
|
yellow_threshold = EXCLUDED.yellow_threshold,
|
|
red_threshold = EXCLUDED.red_threshold;
|
|
|
|
-- ============================================================
|
|
-- 十、创建数据质量检查日志表
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS analytics.data_quality_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
check_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
check_type TEXT NOT NULL, -- bill/dish/inventory/mapping
|
|
total_records BIGINT,
|
|
issue_count INTEGER DEFAULT 0,
|
|
issue_details JSONB,
|
|
status TEXT DEFAULT '通过', -- 通过/警告/失败
|
|
checked_by TEXT DEFAULT 'system',
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- ============================================================
|
|
-- 十一、创建口径变更日志表
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS analytics.metric_version_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
metric_code TEXT NOT NULL,
|
|
version TEXT NOT NULL,
|
|
change_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
change_reason TEXT NOT NULL,
|
|
old_formula TEXT,
|
|
new_formula TEXT,
|
|
impact_amount NUMERIC,
|
|
changed_by TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now()
|
|
);
|
|
|
|
-- ============================================================
|
|
-- 完成提示
|
|
-- ============================================================
|
|
DO $$
|
|
BEGIN
|
|
RAISE NOTICE '本体标准数据库表创建完成';
|
|
END $$;
|