# 2025年10-12月 客户消费能力Top100（总表）
## 思考过程
以台费订单为基准汇总三类明细，满足应付金额口径，并输出Top100客户的消费/充值/助教偏好。按你的要求，先生成评价为空的版本，再在脚本末尾回填评价。

## 查询说明
消费=台费(dwd_table_fee_log.ledger_amount)+助教(dwd_assistant_service_log.ledger_amount)+商品(dwd_store_goods_sale.ledger_amount)，均为应付金额（不扣优惠），以台费订单为基准串联；充值=充值支付流水(dwd_payment.relate_type=5, pay_status=2, pay_amount>0)按支付时间切月；储值卡未使用金额=当前有效储值卡余额之和(dim_member_card_account.balance, member_card_type_name='储值卡')；喜爱助教=基础课时长+附加课时长*1.5（income_seconds换算小时），总表按10-12月汇总Top5。

## SQL

### Top100（按消费总额）
```sql
with base_orders as (
  select
    tfl.order_settle_id,
    max(tfl.member_id) as member_id,
    min(tfl.start_use_time) as order_start_time,
    max(tfl.ledger_end_time) as order_end_time,
    sum(tfl.ledger_amount) as table_amount,
    sum(tfl.real_table_use_seconds) as table_use_seconds
  from billiards_dwd.dwd_table_fee_log tfl
  where tfl.site_id = %(site_id)s
    and coalesce(tfl.is_delete,0) = 0
    and tfl.start_use_time >= '2025-10-01 00:00:00+08'::timestamptz
    and tfl.start_use_time <  '2026-01-01 00:00:00+08'::timestamptz
  group by tfl.order_settle_id
),
assistant_info as (
  select
    asl.order_settle_id,
    sum(asl.ledger_amount) as assistant_amount,
    min(asl.start_use_time) as assistant_start_time,
    max(asl.last_use_time) as assistant_end_time
  from billiards_dwd.dwd_assistant_service_log asl
  join base_orders bo on bo.order_settle_id = asl.order_settle_id
  where asl.site_id = %(site_id)s
    and coalesce(asl.is_delete,0) = 0
  group by asl.order_settle_id
),
goods_amount as (
  select
    g.order_settle_id,
    sum(g.ledger_amount) as goods_amount
  from billiards_dwd.dwd_store_goods_sale g
  join base_orders bo on bo.order_settle_id = g.order_settle_id
  where g.site_id = %(site_id)s
    and coalesce(g.is_delete,0) = 0
  group by g.order_settle_id
),
orders as (
  select
    bo.order_settle_id,
    bo.member_id,
    least(bo.order_start_time, coalesce(a.assistant_start_time, bo.order_start_time)) as order_start_time,
    greatest(bo.order_end_time, coalesce(a.assistant_end_time, bo.order_end_time)) as order_end_time,
    bo.table_use_seconds,
    coalesce(bo.table_amount,0) + coalesce(a.assistant_amount,0) + coalesce(g.goods_amount,0) as order_amount
  from base_orders bo
  left join assistant_info a on a.order_settle_id = bo.order_settle_id
  left join goods_amount g on g.order_settle_id = bo.order_settle_id
)

select
  o.member_id,
  sum(o.order_amount) as consume_total,
  count(*) as order_cnt
from orders o
where o.member_id is not null and o.member_id <> 0
group by o.member_id
order by consume_total desc
limit 100;
```

### 按月消费汇总
```sql
with base_orders as (
  select
    tfl.order_settle_id,
    max(tfl.member_id) as member_id,
    min(tfl.start_use_time) as order_start_time,
    max(tfl.ledger_end_time) as order_end_time,
    sum(tfl.ledger_amount) as table_amount,
    sum(tfl.real_table_use_seconds) as table_use_seconds
  from billiards_dwd.dwd_table_fee_log tfl
  where tfl.site_id = %(site_id)s
    and coalesce(tfl.is_delete,0) = 0
    and tfl.start_use_time >= '2025-10-01 00:00:00+08'::timestamptz
    and tfl.start_use_time <  '2026-01-01 00:00:00+08'::timestamptz
  group by tfl.order_settle_id
),
assistant_info as (
  select
    asl.order_settle_id,
    sum(asl.ledger_amount) as assistant_amount,
    min(asl.start_use_time) as assistant_start_time,
    max(asl.last_use_time) as assistant_end_time
  from billiards_dwd.dwd_assistant_service_log asl
  join base_orders bo on bo.order_settle_id = asl.order_settle_id
  where asl.site_id = %(site_id)s
    and coalesce(asl.is_delete,0) = 0
  group by asl.order_settle_id
),
goods_amount as (
  select
    g.order_settle_id,
    sum(g.ledger_amount) as goods_amount
  from billiards_dwd.dwd_store_goods_sale g
  join base_orders bo on bo.order_settle_id = g.order_settle_id
  where g.site_id = %(site_id)s
    and coalesce(g.is_delete,0) = 0
  group by g.order_settle_id
),
orders as (
  select
    bo.order_settle_id,
    bo.member_id,
    least(bo.order_start_time, coalesce(a.assistant_start_time, bo.order_start_time)) as order_start_time,
    greatest(bo.order_end_time, coalesce(a.assistant_end_time, bo.order_end_time)) as order_end_time,
    bo.table_use_seconds,
    coalesce(bo.table_amount,0) + coalesce(a.assistant_amount,0) + coalesce(g.goods_amount,0) as order_amount
  from base_orders bo
  left join assistant_info a on a.order_settle_id = bo.order_settle_id
  left join goods_amount g on g.order_settle_id = bo.order_settle_id
)

, x as (
  select
    o.member_id,
    case when o.order_start_time >= '2025-10-01 00:00:00+08'::timestamptz and o.order_start_time < '2025-11-01 00:00:00+08'::timestamptz then '2025-10' when o.order_start_time >= '2025-11-01 00:00:00+08'::timestamptz and o.order_start_time < '2025-12-01 00:00:00+08'::timestamptz then '2025-11' when o.order_start_time >= '2025-12-01 00:00:00+08'::timestamptz and o.order_start_time < '2026-01-01 00:00:00+08'::timestamptz then '2025-12' else null end as month_key,
    o.order_amount
  from orders o
  where o.member_id is not null and o.member_id <> 0
)
select
  member_id,
  month_key,
  sum(order_amount) as consume_sum
from x
where month_key is not null
group by member_id, month_key;
```

### 按月充值汇总
```sql
with pay as (
  select
    p.pay_time,
    r.member_id,
    p.pay_amount
  from billiards_dwd.dwd_payment p
  join billiards_dwd.dwd_recharge_order r on r.recharge_order_id = p.relate_id
  where p.site_id = %(site_id)s
    and p.relate_type = 5
    and p.pay_status = 2
    and p.pay_amount > 0
    and p.pay_time >= %(window_start)s::timestamptz
    and p.pay_time <  %(window_end)s::timestamptz
)
, x as (
  select
    member_id,
    case when pay_time >= '2025-10-01 00:00:00+08'::timestamptz and pay_time < '2025-11-01 00:00:00+08'::timestamptz then '2025-10' when pay_time >= '2025-11-01 00:00:00+08'::timestamptz and pay_time < '2025-12-01 00:00:00+08'::timestamptz then '2025-11' when pay_time >= '2025-12-01 00:00:00+08'::timestamptz and pay_time < '2026-01-01 00:00:00+08'::timestamptz then '2025-12' else null end as month_key,
    pay_amount
  from pay
)
select
  member_id,
  month_key,
  sum(pay_amount) as recharge_sum
from x
where month_key is not null
group by member_id, month_key;
```

### 储值卡未使用金额（当前余额汇总）
```sql
select
  tenant_member_id as member_id,
  sum(balance) as stored_value_balance
from billiards_dwd.dim_member_card_account
where scd2_is_current=1
  and coalesce(is_delete,0)=0
  and member_card_type_name='储值卡'
  and tenant_member_id = any(%(ids)s)
group by tenant_member_id;
```

### 喜爱助教Top5（10-12月）
```sql
with x as (
  select
    asl.tenant_member_id as member_id,
    asl.nickname as assistant_nickname,
    sum(case when asl.order_assistant_type=1 then asl.income_seconds else asl.income_seconds*1.5 end) / 3600.0 as weighted_hours
  from billiards_dwd.dwd_assistant_service_log asl
  where asl.site_id = %(site_id)s
    and coalesce(asl.is_delete,0)=0
    and asl.tenant_member_id is not null and asl.tenant_member_id <> 0
    and asl.start_use_time >= %(window_start)s::timestamptz
    and asl.start_use_time <  %(window_end)s::timestamptz
  group by asl.tenant_member_id, asl.nickname
),
ranked as (
  select *, row_number() over(partition by member_id order by weighted_hours desc) as rn
  from x
)
select
  member_id,
  string_agg(assistant_nickname || '(' || to_char(round(weighted_hours::numeric, 1), 'FM999999990.0') || 'h)', '、' order by weighted_hours desc) as fav5
from ranked
where rn <= 5
group by member_id;
```

### 评价画像：订单/时长/到店日期
```sql
with base_orders as (
  select
    tfl.order_settle_id,
    max(tfl.member_id) as member_id,
    min(tfl.start_use_time) as order_start_time,
    max(tfl.ledger_end_time) as order_end_time,
    sum(tfl.ledger_amount) as table_amount,
    sum(tfl.real_table_use_seconds) as table_use_seconds
  from billiards_dwd.dwd_table_fee_log tfl
  where tfl.site_id = %(site_id)s
    and coalesce(tfl.is_delete,0) = 0
    and tfl.start_use_time >= '2025-10-01 00:00:00+08'::timestamptz
    and tfl.start_use_time <  '2026-01-01 00:00:00+08'::timestamptz
  group by tfl.order_settle_id
),
assistant_info as (
  select
    asl.order_settle_id,
    sum(asl.ledger_amount) as assistant_amount,
    min(asl.start_use_time) as assistant_start_time,
    max(asl.last_use_time) as assistant_end_time
  from billiards_dwd.dwd_assistant_service_log asl
  join base_orders bo on bo.order_settle_id = asl.order_settle_id
  where asl.site_id = %(site_id)s
    and coalesce(asl.is_delete,0) = 0
  group by asl.order_settle_id
),
goods_amount as (
  select
    g.order_settle_id,
    sum(g.ledger_amount) as goods_amount
  from billiards_dwd.dwd_store_goods_sale g
  join base_orders bo on bo.order_settle_id = g.order_settle_id
  where g.site_id = %(site_id)s
    and coalesce(g.is_delete,0) = 0
  group by g.order_settle_id
),
orders as (
  select
    bo.order_settle_id,
    bo.member_id,
    least(bo.order_start_time, coalesce(a.assistant_start_time, bo.order_start_time)) as order_start_time,
    greatest(bo.order_end_time, coalesce(a.assistant_end_time, bo.order_end_time)) as order_end_time,
    bo.table_use_seconds,
    coalesce(bo.table_amount,0) + coalesce(a.assistant_amount,0) + coalesce(g.goods_amount,0) as order_amount
  from base_orders bo
  left join assistant_info a on a.order_settle_id = bo.order_settle_id
  left join goods_amount g on g.order_settle_id = bo.order_settle_id
)

select
  o.member_id,
  count(*) as orders,
  avg(o.order_amount) as avg_order,
  sum(o.table_use_seconds)/3600.0 as play_hours,
  avg(o.table_use_seconds)/3600.0 as avg_play_hours,
  min((o.order_start_time at time zone 'Asia/Shanghai')::date) as first_visit_day,
  max((o.order_start_time at time zone 'Asia/Shanghai')::date) as last_visit_day,
  count(distinct (o.order_start_time at time zone 'Asia/Shanghai')::date) as visit_days,
  sum(case when o.order_start_time >= '2025-10-01 00:00:00+08'::timestamptz and o.order_start_time < '2025-11-01 00:00:00+08'::timestamptz then 1 else 0 end) as orders_oct,
  sum(case when o.order_start_time >= '2025-11-01 00:00:00+08'::timestamptz and o.order_start_time < '2025-12-01 00:00:00+08'::timestamptz then 1 else 0 end) as orders_nov,
  sum(case when o.order_start_time >= '2025-12-01 00:00:00+08'::timestamptz and o.order_start_time < '2026-01-01 00:00:00+08'::timestamptz then 1 else 0 end) as orders_dec
from orders o
where o.member_id = any(%(ids)s)
group by o.member_id;
```

### 评价画像：到店/离店时间（小时）
```sql
with base_orders as (
  select
    tfl.order_settle_id,
    max(tfl.member_id) as member_id,
    min(tfl.start_use_time) as order_start_time,
    max(tfl.ledger_end_time) as order_end_time,
    sum(tfl.ledger_amount) as table_amount,
    sum(tfl.real_table_use_seconds) as table_use_seconds
  from billiards_dwd.dwd_table_fee_log tfl
  where tfl.site_id = %(site_id)s
    and coalesce(tfl.is_delete,0) = 0
    and tfl.start_use_time >= '2025-10-01 00:00:00+08'::timestamptz
    and tfl.start_use_time <  '2026-01-01 00:00:00+08'::timestamptz
  group by tfl.order_settle_id
),
assistant_info as (
  select
    asl.order_settle_id,
    sum(asl.ledger_amount) as assistant_amount,
    min(asl.start_use_time) as assistant_start_time,
    max(asl.last_use_time) as assistant_end_time
  from billiards_dwd.dwd_assistant_service_log asl
  join base_orders bo on bo.order_settle_id = asl.order_settle_id
  where asl.site_id = %(site_id)s
    and coalesce(asl.is_delete,0) = 0
  group by asl.order_settle_id
),
goods_amount as (
  select
    g.order_settle_id,
    sum(g.ledger_amount) as goods_amount
  from billiards_dwd.dwd_store_goods_sale g
  join base_orders bo on bo.order_settle_id = g.order_settle_id
  where g.site_id = %(site_id)s
    and coalesce(g.is_delete,0) = 0
  group by g.order_settle_id
),
orders as (
  select
    bo.order_settle_id,
    bo.member_id,
    least(bo.order_start_time, coalesce(a.assistant_start_time, bo.order_start_time)) as order_start_time,
    greatest(bo.order_end_time, coalesce(a.assistant_end_time, bo.order_end_time)) as order_end_time,
    bo.table_use_seconds,
    coalesce(bo.table_amount,0) + coalesce(a.assistant_amount,0) + coalesce(g.goods_amount,0) as order_amount
  from base_orders bo
  left join assistant_info a on a.order_settle_id = bo.order_settle_id
  left join goods_amount g on g.order_settle_id = bo.order_settle_id
)

, t as (
  select
    o.member_id,
    extract(hour from (o.order_start_time at time zone 'Asia/Shanghai'))
      + extract(minute from (o.order_start_time at time zone 'Asia/Shanghai'))/60.0
      + extract(second from (o.order_start_time at time zone 'Asia/Shanghai'))/3600.0 as arrive_h,
    extract(hour from (o.order_end_time at time zone 'Asia/Shanghai'))
      + extract(minute from (o.order_end_time at time zone 'Asia/Shanghai'))/60.0
      + extract(second from (o.order_end_time at time zone 'Asia/Shanghai'))/3600.0 as leave_h_raw
  from orders o
  where o.member_id = any(%(ids)s)
),
tt as (
  select
    member_id,
    arrive_h,
    case when leave_h_raw < arrive_h then leave_h_raw + 24 else leave_h_raw end as leave_h
  from t
)
select
  member_id,
  avg(arrive_h) as arrive_avg_h,
  percentile_cont(0.5) within group (order by arrive_h) as arrive_med_h,
  avg(leave_h) as leave_avg_h,
  percentile_cont(0.5) within group (order by leave_h) as leave_med_h
from tt
group by member_id;
```

### 评价画像：球台分区偏好（按时长）
```sql
select
  tfl.member_id,
  coalesce(tfl.site_table_area_name,'') as site_table_area_name,
  sum(tfl.real_table_use_seconds)/3600.0 as hours
from billiards_dwd.dwd_table_fee_log tfl
where tfl.site_id=%(site_id)s and coalesce(tfl.is_delete,0)=0
  and tfl.start_use_time >= %(window_start)s::timestamptz and tfl.start_use_time < %(window_end)s::timestamptz
  and tfl.member_id = any(%(ids)s)
group by tfl.member_id, site_table_area_name;
```

### 评价画像：商品明细（名称+数量）
```sql
with base_orders as (
  select order_settle_id, max(member_id) as member_id
  from billiards_dwd.dwd_table_fee_log
  where site_id=%(site_id)s and coalesce(is_delete,0)=0
    and start_use_time >= %(window_start)s::timestamptz and start_use_time < %(window_end)s::timestamptz
  group by order_settle_id
)
select
  bo.member_id,
  g.ledger_name,
  sum(g.ledger_count) as qty,
  sum(g.ledger_amount) as amount
from base_orders bo
join billiards_dwd.dwd_store_goods_sale g on g.order_settle_id = bo.order_settle_id
where g.site_id=%(site_id)s and coalesce(g.is_delete,0)=0
  and bo.member_id = any(%(ids)s)
group by bo.member_id, g.ledger_name;
```

### 评价画像：到店日期明细（用于周期/近期分析）
```sql
with base_orders as (
  select
    tfl.order_settle_id,
    max(tfl.member_id) as member_id,
    min(tfl.start_use_time) as order_start_time,
    max(tfl.ledger_end_time) as order_end_time,
    sum(tfl.ledger_amount) as table_amount,
    sum(tfl.real_table_use_seconds) as table_use_seconds
  from billiards_dwd.dwd_table_fee_log tfl
  where tfl.site_id = %(site_id)s
    and coalesce(tfl.is_delete,0) = 0
    and tfl.start_use_time >= '2025-10-01 00:00:00+08'::timestamptz
    and tfl.start_use_time <  '2026-01-01 00:00:00+08'::timestamptz
  group by tfl.order_settle_id
),
assistant_info as (
  select
    asl.order_settle_id,
    sum(asl.ledger_amount) as assistant_amount,
    min(asl.start_use_time) as assistant_start_time,
    max(asl.last_use_time) as assistant_end_time
  from billiards_dwd.dwd_assistant_service_log asl
  join base_orders bo on bo.order_settle_id = asl.order_settle_id
  where asl.site_id = %(site_id)s
    and coalesce(asl.is_delete,0) = 0
  group by asl.order_settle_id
),
goods_amount as (
  select
    g.order_settle_id,
    sum(g.ledger_amount) as goods_amount
  from billiards_dwd.dwd_store_goods_sale g
  join base_orders bo on bo.order_settle_id = g.order_settle_id
  where g.site_id = %(site_id)s
    and coalesce(g.is_delete,0) = 0
  group by g.order_settle_id
),
orders as (
  select
    bo.order_settle_id,
    bo.member_id,
    least(bo.order_start_time, coalesce(a.assistant_start_time, bo.order_start_time)) as order_start_time,
    greatest(bo.order_end_time, coalesce(a.assistant_end_time, bo.order_end_time)) as order_end_time,
    bo.table_use_seconds,
    coalesce(bo.table_amount,0) + coalesce(a.assistant_amount,0) + coalesce(g.goods_amount,0) as order_amount
  from base_orders bo
  left join assistant_info a on a.order_settle_id = bo.order_settle_id
  left join goods_amount g on g.order_settle_id = bo.order_settle_id
)

select
  o.member_id,
  (o.order_start_time at time zone 'Asia/Shanghai')::date as visit_date,
  count(*) as orders,
  sum(o.order_amount) as amount
from orders o
where o.member_id = any(%(ids)s)
group by o.member_id, visit_date
order by o.member_id, visit_date;
```
