dalongxia/product_id_promo_weekly_report.sql
2026-03-04 13:25:48 +08:00

405 lines
14 KiB
Transact-SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

USE [JSDataMiddlePlatform];
SET NOCOUNT ON;
/*
Template: 商品ID推广费用周报(2026可用)
目标: 费用 + 发货件数 + 缺货风险 + 负责人
默认窗口: 上一个自然周 [week_start, week_end)
*/
DECLARE @week_end datetime2 = CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) AS datetime2);
DECLARE @week_start datetime2 = CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()) - 1, 0) AS datetime2);
DECLARE @lookback_30d datetime2 = DATEADD(day, -30, @week_end);
DECLARE @lead_days int = 10;
DECLARE @warn_days int = 15;
IF OBJECT_ID('tempdb..#base_week') IS NOT NULL
DROP TABLE #base_week;
WITH src AS (
SELECT
NULLIF(a.undefined2, '') AS order_id,
NULLIF(a.undefined12, '') AS product_id,
NULLIF(a.undefined10, '') AS sku_id,
ISNULL(NULLIF(a.undefined8, ''), 'unknown') AS shop_name,
CASE
WHEN CHARINDEX('-', ISNULL(NULLIF(a.undefined8, ''), '')) > 0
THEN LEFT(ISNULL(NULLIF(a.undefined8, ''), ''), CHARINDEX('-', ISNULL(NULLIF(a.undefined8, ''), '')) - 1)
ELSE 'unknown'
END AS shop_channel,
COALESCE(
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 120),
TRY_CONVERT(datetime2, REPLACE(NULLIF(a.undefined6, ''), '/', '-'), 120),
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 111),
TRY_CONVERT(datetime2, a.createdAt)
) AS ship_dt,
COALESCE(
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined30, '')),
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined20, '')),
CAST(0 AS decimal(18, 2))
) AS shipped_qty,
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined97, '')) AS promo_fee_8001201,
(
ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined108, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined109, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined110, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined111, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined112, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined113, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined114, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined115, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined116, '')), 0)
+ ISNULL(TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined117, '')), 0)
) AS commission_fee_sum,
ISNULL(NULLIF(a.undefined62, ''), '') AS order_labels
FROM dbo.jst_goods_profit_kpi_items_afs AS a WITH (NOLOCK)
)
SELECT
order_id,
product_id,
sku_id,
shop_name,
shop_channel,
ship_dt,
shipped_qty,
ISNULL(promo_fee_8001201, 0) AS promo_fee_8001201,
ISNULL(commission_fee_sum, 0) AS commission_fee_sum
INTO #base_week
FROM src
WHERE ship_dt >= @week_start
AND ship_dt < @week_end
AND ISNULL(product_id, '') <> ''
AND ISNULL(sku_id, '') <> ''
AND order_labels NOT LIKE '%配件%'
AND order_labels NOT LIKE '%特殊单%';
IF OBJECT_ID('tempdb..#ship_30d') IS NOT NULL
DROP TABLE #ship_30d;
WITH src_30d AS (
SELECT
NULLIF(a.undefined12, '') AS product_id,
NULLIF(a.undefined10, '') AS sku_id,
ISNULL(NULLIF(a.undefined8, ''), 'unknown') AS shop_name,
COALESCE(
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 120),
TRY_CONVERT(datetime2, REPLACE(NULLIF(a.undefined6, ''), '/', '-'), 120),
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 111),
TRY_CONVERT(datetime2, a.createdAt)
) AS ship_dt,
COALESCE(
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined30, '')),
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined20, '')),
CAST(0 AS decimal(18, 2))
) AS shipped_qty,
ISNULL(NULLIF(a.undefined62, ''), '') AS order_labels
FROM dbo.jst_goods_profit_kpi_items_afs AS a WITH (NOLOCK)
)
SELECT
product_id,
sku_id,
shop_name,
SUM(ISNULL(shipped_qty, 0)) AS shipped_qty_30d
INTO #ship_30d
FROM src_30d
WHERE ship_dt >= @lookback_30d
AND ship_dt < @week_end
AND ISNULL(product_id, '') <> ''
AND ISNULL(sku_id, '') <> ''
AND order_labels NOT LIKE '%配件%'
AND order_labels NOT LIKE '%特殊单%'
GROUP BY product_id, sku_id, shop_name;
IF OBJECT_ID('tempdb..#inv_latest') IS NOT NULL
DROP TABLE #inv_latest;
WITH inv_rank AS (
SELECT
NULLIF(i.sku_id, '') AS sku_id,
TRY_CONVERT(decimal(18, 2), i.qty) AS stock_qty,
TRY_CONVERT(decimal(18, 2), i.order_lock) AS order_lock_qty,
TRY_CONVERT(decimal(18, 2), i.pick_lock) AS pick_lock_qty,
TRY_CONVERT(decimal(18, 2), i.lock_qty) AS lock_qty,
i.name AS inventory_note,
COALESCE(i.updateTime, i.modified, i.createTime) AS inventory_snapshot_time,
ROW_NUMBER() OVER (
PARTITION BY NULLIF(i.sku_id, '')
ORDER BY COALESCE(i.updateTime, i.modified, i.createTime) DESC, i.Id DESC
) AS rn
FROM dbo.jst_inventoryInfo AS i WITH (NOLOCK)
WHERE NULLIF(i.sku_id, '') IN (SELECT DISTINCT sku_id FROM #base_week)
)
SELECT
sku_id,
stock_qty,
order_lock_qty,
pick_lock_qty,
lock_qty,
inventory_note,
inventory_snapshot_time,
CASE
WHEN ISNULL(stock_qty, 0) - ISNULL(order_lock_qty, 0) - ISNULL(pick_lock_qty, 0) - ISNULL(lock_qty, 0) < 0
THEN CAST(0 AS decimal(18, 2))
ELSE ISNULL(stock_qty, 0) - ISNULL(order_lock_qty, 0) - ISNULL(pick_lock_qty, 0) - ISNULL(lock_qty, 0)
END AS available_qty
INTO #inv_latest
FROM inv_rank
WHERE rn = 1;
IF OBJECT_ID('tempdb..#sku_risk') IS NOT NULL
DROP TABLE #sku_risk;
SELECT
s.product_id,
s.shop_name,
s.sku_id,
s.shipped_qty_30d,
CAST(ISNULL(s.shipped_qty_30d, 0) / 30.0 AS decimal(18, 2)) AS avg_daily_shipped_30d,
inv.available_qty,
CAST(
CASE
WHEN ISNULL(s.shipped_qty_30d, 0) <= 0 THEN NULL
ELSE inv.available_qty / NULLIF(ISNULL(s.shipped_qty_30d, 0) / 30.0, 0)
END AS decimal(18, 2)
) AS estimated_stockout_days,
CAST(
CASE
WHEN ISNULL(s.shipped_qty_30d, 0) <= 0 THEN 0
WHEN inv.available_qty >= (ISNULL(s.shipped_qty_30d, 0) / 30.0) * @lead_days THEN 0
ELSE ((ISNULL(s.shipped_qty_30d, 0) / 30.0) * @lead_days) - inv.available_qty
END AS decimal(18, 2)
) AS suggest_replenish_qty_to_10d,
CASE
WHEN inv.sku_id IS NULL THEN '无库存快照'
WHEN ISNULL(s.shipped_qty_30d, 0) <= 0 THEN '近30天无发货'
WHEN inv.available_qty <= 0 THEN '缺货/可用库存<=0'
WHEN (inv.available_qty / NULLIF(ISNULL(s.shipped_qty_30d, 0) / 30.0, 0)) <= @lead_days THEN '补货紧急(<=10天)'
WHEN (inv.available_qty / NULLIF(ISNULL(s.shipped_qty_30d, 0) / 30.0, 0)) <= @warn_days THEN '补货预警(10-15天)'
ELSE '库存安全'
END AS stock_risk_level
INTO #sku_risk
FROM #ship_30d AS s
LEFT JOIN #inv_latest AS inv
ON inv.sku_id = s.sku_id;
IF OBJECT_ID('tempdb..#owner_pick') IS NOT NULL
DROP TABLE #owner_pick;
WITH owner_src AS (
SELECT
bw.product_id,
bw.shop_name,
bw.sku_id,
SUM(ISNULL(bw.shipped_qty, 0)) AS week_shipped_qty,
NULLIF(js.other_1, '') AS spu,
NULLIF(js.i_id, '') AS style_code,
NULLIF(tm.oper, '') AS tm_oper,
NULLIF(opm.oper, '') AS opm_oper,
NULLIF(spu_cfg.oper, '') AS spu_cfg_oper,
NULLIF(spu_cfg.purchaser, '') AS purchaser
FROM #base_week AS bw
LEFT JOIN dbo.jst_sku AS js WITH (NOLOCK)
ON js.sku_id = bw.sku_id
LEFT JOIN dbo.op_TargetManagement AS tm WITH (NOLOCK)
ON NULLIF(tm.shopproid, '') = bw.product_id
AND ISNULL(NULLIF(tm.shopname, ''), '') = ISNULL(NULLIF(bw.shop_name, ''), '')
OUTER APPLY (
SELECT TOP 1 o.oper
FROM dbo.op_operManage AS o WITH (NOLOCK)
WHERE NULLIF(o.spu, '') IN (
NULLIF(js.other_1, ''),
NULLIF(js.i_id, ''),
CASE
WHEN CHARINDEX('K', ISNULL(js.other_1, '')) > 0
THEN LEFT(js.other_1, CHARINDEX('K', js.other_1) - 1)
ELSE NULL
END
)
ORDER BY o.Id DESC
) AS opm
OUTER APPLY (
SELECT TOP 1 c.oper, c.purchaser
FROM dbo.fys_SkuDataLibrary_SpuConfig AS c WITH (NOLOCK)
WHERE NULLIF(c.spu, '') IN (
NULLIF(js.other_1, ''),
NULLIF(js.i_id, ''),
CASE
WHEN CHARINDEX('K', ISNULL(js.other_1, '')) > 0
THEN LEFT(js.other_1, CHARINDEX('K', js.other_1) - 1)
ELSE NULL
END
)
ORDER BY c.Id DESC
) AS spu_cfg
GROUP BY
bw.product_id,
bw.shop_name,
bw.sku_id,
NULLIF(js.other_1, ''),
NULLIF(js.i_id, ''),
NULLIF(tm.oper, ''),
NULLIF(opm.oper, ''),
NULLIF(spu_cfg.oper, ''),
NULLIF(spu_cfg.purchaser, '')
), owner_rank AS (
SELECT
product_id,
shop_name,
COALESCE(tm_oper, opm_oper, spu_cfg_oper, '未分配') AS operator_name,
COALESCE(purchaser, '未分配') AS purchaser_name,
ROW_NUMBER() OVER (
PARTITION BY product_id, shop_name
ORDER BY week_shipped_qty DESC, sku_id ASC
) AS rn
FROM owner_src
)
SELECT
product_id,
shop_name,
operator_name,
purchaser_name
INTO #owner_pick
FROM owner_rank
WHERE rn = 1;
IF OBJECT_ID('tempdb..#risk_agg') IS NOT NULL
DROP TABLE #risk_agg;
SELECT
product_id,
shop_name,
COUNT(DISTINCT CASE WHEN stock_risk_level LIKE '补货紧急%' OR stock_risk_level LIKE '缺货%' THEN sku_id END) AS urgent_risk_sku_count,
COUNT(DISTINCT CASE WHEN stock_risk_level LIKE '补货预警%' THEN sku_id END) AS warning_risk_sku_count,
MIN(estimated_stockout_days) AS min_estimated_stockout_days,
SUM(CASE WHEN stock_risk_level LIKE '补货紧急%' OR stock_risk_level LIKE '缺货%' THEN ISNULL(suggest_replenish_qty_to_10d, 0) ELSE 0 END) AS suggest_replenish_qty_10d
INTO #risk_agg
FROM #sku_risk
GROUP BY product_id, shop_name;
IF OBJECT_ID('tempdb..#product_week') IS NOT NULL
DROP TABLE #product_week;
SELECT
bw.product_id,
bw.shop_channel,
bw.shop_name,
COUNT(DISTINCT bw.sku_id) AS sku_count,
COUNT(DISTINCT bw.order_id) AS order_count,
SUM(ISNULL(bw.shipped_qty, 0)) AS shipped_qty_sum,
SUM(ISNULL(bw.promo_fee_8001201, 0)) AS promo_fee_sum,
SUM(ISNULL(bw.commission_fee_sum, 0)) AS commission_fee_sum,
SUM(ISNULL(bw.promo_fee_8001201, 0) + ISNULL(bw.commission_fee_sum, 0)) AS marketing_fee_sum,
CASE
WHEN SUM(ISNULL(bw.shipped_qty, 0)) <= 0 THEN NULL
ELSE (SUM(ISNULL(bw.promo_fee_8001201, 0) + ISNULL(bw.commission_fee_sum, 0)) / SUM(ISNULL(bw.shipped_qty, 0)))
END AS marketing_fee_per_piece,
ISNULL(ra.urgent_risk_sku_count, 0) AS urgent_risk_sku_count,
ISNULL(ra.warning_risk_sku_count, 0) AS warning_risk_sku_count,
ra.min_estimated_stockout_days,
ISNULL(ra.suggest_replenish_qty_10d, 0) AS suggest_replenish_qty_10d,
CASE
WHEN ISNULL(ra.urgent_risk_sku_count, 0) > 0 THEN '补货紧急'
WHEN ISNULL(ra.warning_risk_sku_count, 0) > 0 THEN '补货预警'
ELSE '库存安全/无快照'
END AS product_stock_risk_level,
ISNULL(op.purchaser_name, '未分配') AS purchaser_name,
ISNULL(op.operator_name, '未分配') AS operator_name
INTO #product_week
FROM #base_week AS bw
LEFT JOIN #risk_agg AS ra
ON ra.product_id = bw.product_id
AND ra.shop_name = bw.shop_name
LEFT JOIN #owner_pick AS op
ON op.product_id = bw.product_id
AND op.shop_name = bw.shop_name
GROUP BY
bw.product_id,
bw.shop_channel,
bw.shop_name,
ISNULL(ra.urgent_risk_sku_count, 0),
ISNULL(ra.warning_risk_sku_count, 0),
ra.min_estimated_stockout_days,
ISNULL(ra.suggest_replenish_qty_10d, 0),
CASE
WHEN ISNULL(ra.urgent_risk_sku_count, 0) > 0 THEN '补货紧急'
WHEN ISNULL(ra.warning_risk_sku_count, 0) > 0 THEN '补货预警'
ELSE '库存安全/无快照'
END,
ISNULL(op.purchaser_name, '未分配'),
ISNULL(op.operator_name, '未分配');
-- 1) 周汇总(商品ID维度)
SELECT
@week_start AS week_start,
@week_end AS week_end,
COUNT(*) AS product_shop_count,
COUNT(DISTINCT product_id) AS product_count,
COUNT(DISTINCT shop_name) AS shop_count,
SUM(ISNULL(order_count, 0)) AS order_count_sum,
SUM(ISNULL(shipped_qty_sum, 0)) AS shipped_qty_sum,
SUM(ISNULL(promo_fee_sum, 0)) AS promo_fee_sum,
SUM(ISNULL(commission_fee_sum, 0)) AS commission_fee_sum,
SUM(ISNULL(marketing_fee_sum, 0)) AS marketing_fee_sum,
CASE
WHEN SUM(ISNULL(shipped_qty_sum, 0)) <= 0 THEN NULL
ELSE SUM(ISNULL(marketing_fee_sum, 0)) / SUM(ISNULL(shipped_qty_sum, 0))
END AS marketing_fee_per_piece_avg,
SUM(CASE WHEN product_stock_risk_level = '补货紧急' THEN 1 ELSE 0 END) AS urgent_product_count,
SUM(CASE WHEN product_stock_risk_level = '补货预警' THEN 1 ELSE 0 END) AS warning_product_count
FROM #product_week;
-- 2) 商品ID周报TOP100(费用 + 发货件数 + 缺货风险 + 负责人)
SELECT TOP (100)
product_id,
shop_channel,
shop_name,
sku_count,
order_count,
shipped_qty_sum,
promo_fee_sum,
commission_fee_sum,
marketing_fee_sum,
marketing_fee_per_piece,
product_stock_risk_level,
min_estimated_stockout_days,
suggest_replenish_qty_10d,
urgent_risk_sku_count,
warning_risk_sku_count,
purchaser_name,
operator_name
FROM #product_week
ORDER BY marketing_fee_sum DESC, shipped_qty_sum DESC, product_id ASC;
-- 3) 高风险商品ID清单(运营动作优先)
SELECT TOP (100)
product_id,
shop_channel,
shop_name,
shipped_qty_sum,
marketing_fee_sum,
product_stock_risk_level,
min_estimated_stockout_days,
suggest_replenish_qty_10d,
purchaser_name,
operator_name
FROM #product_week
WHERE product_stock_risk_level IN ('补货紧急', '补货预警')
ORDER BY
CASE product_stock_risk_level WHEN '补货紧急' THEN 1 ELSE 2 END,
min_estimated_stockout_days ASC,
marketing_fee_sum DESC;
-- 4) 负责人缺失清单(数据治理)
SELECT TOP (100)
product_id,
shop_channel,
shop_name,
shipped_qty_sum,
marketing_fee_sum,
purchaser_name,
operator_name
FROM #product_week
WHERE purchaser_name = '未分配'
OR operator_name = '未分配'
ORDER BY marketing_fee_sum DESC, shipped_qty_sum DESC;