Files
LabelChange-server/database/migrations/004_create_sp_operations_monitor.sql
2026-06-01 16:30:29 +08:00

473 lines
22 KiB
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.

DROP PROCEDURE IF EXISTS sp_GetOperationsMonitor;
DELIMITER $$
CREATE PROCEDURE sp_GetOperationsMonitor()
BEGIN
DROP TEMPORARY TABLE IF EXISTS tmp_scan_agg;
DROP TEMPORARY TABLE IF EXISTS tmp_daily_scan;
DROP TEMPORARY TABLE IF EXISTS tmp_form_order;
DROP TEMPORARY TABLE IF EXISTS tmp_form_first_scan;
DROP TEMPORARY TABLE IF EXISTS tmp_form_stats;
DROP TEMPORARY TABLE IF EXISTS tmp_form_push_ranked;
DROP TEMPORARY TABLE IF EXISTS tmp_order_full;
DROP TEMPORARY TABLE IF EXISTS tmp_should_replace_dedup;
DROP TEMPORARY TABLE IF EXISTS tmp_label_push;
DROP TEMPORARY TABLE IF EXISTS tmp_daily_metrics;
-- ================================================================
-- 步骤1atmp_scan_agg - 每个运单的扫描摘要
-- 一次全量扫描 label_scan_history消除后续所有重复扫描
-- ================================================================
CREATE TEMPORARY TABLE tmp_scan_agg (
NeutralWaybillNumber VARCHAR(100) NOT NULL,
MinScanTime DATETIME NULL,
FirstSuccessTime_UTC5 DATETIME NULL,
FirstSuccessDate_UTC5 DATE NULL,
IsSuccess TINYINT NOT NULL DEFAULT 0,
IsStopLabel TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (NeutralWaybillNumber)
);
INSERT INTO tmp_scan_agg
SELECT
NeutralWaybillNumber,
MIN(CreatedAt),
MIN(CASE WHEN Result = 0 THEN CONVERT_TZ(CreatedAt, '+00:00', '-05:00') END),
DATE(MIN(CASE WHEN Result = 0 THEN CONVERT_TZ(CreatedAt, '+00:00', '-05:00') END)),
MAX(CASE WHEN Result = 0 THEN 1 ELSE 0 END),
MAX(CASE WHEN Result = 0 AND Description LIKE '%成功返回STOP标签%' THEN 1 ELSE 0 END)
FROM label_scan_history
GROUP BY NeutralWaybillNumber;
-- ================================================================
-- 步骤1btmp_daily_scan - 每日扫描统计(扫描数/完成数/失败数/STOP数
-- 与步骤1a共用同一次 label_scan_history 扫描
-- ================================================================
CREATE TEMPORARY TABLE tmp_daily_scan (
DATE NOT NULL,
INT NOT NULL DEFAULT 0,
INT NOT NULL DEFAULT 0,
INT NOT NULL DEFAULT 0,
STOP数 INT NOT NULL DEFAULT 0,
PRIMARY KEY ()
);
INSERT INTO tmp_daily_scan (, , , STOP数)
SELECT
DATE(CONVERT_TZ(CreatedAt, '+00:00', '-05:00')),
COUNT(*),
COUNT(DISTINCT CASE WHEN Result = 0 THEN NeutralWaybillNumber END),
COUNT(DISTINCT CASE WHEN Result = 0 AND Description LIKE '%成功返回STOP标签%' THEN NeutralWaybillNumber END)
FROM label_scan_history
GROUP BY DATE(CONVERT_TZ(CreatedAt, '+00:00', '-05:00'));
UPDATE tmp_daily_scan ds
INNER JOIN (
SELECT
DATE(CONVERT_TZ(h.CreatedAt, '+00:00', '-05:00')) AS ,
COUNT(DISTINCT h.NeutralWaybillNumber) AS
FROM label_scan_history h
INNER JOIN tmp_scan_agg sa ON h.NeutralWaybillNumber = sa.NeutralWaybillNumber
WHERE h.Result != 0
AND sa.IsSuccess = 0
GROUP BY DATE(CONVERT_TZ(h.CreatedAt, '+00:00', '-05:00'))
) fail_data ON ds. = fail_data.
SET ds. = fail_data.;
-- ================================================================
-- 步骤2tmp_form_order - 关联交接单和换单请求
-- 先对 arrival_handover_forms 两次索引查找HandoverNumber 有唯一索引)
-- 加上步骤1新建的 idx_master_package_number / idx_bill_of_lading_number 索引
-- ================================================================
CREATE TEMPORARY TABLE tmp_form_order (
FormId INT NOT NULL,
HandoverNumber VARCHAR(100) NOT NULL,
ReceiptTime DATETIME NULL,
ReceiptDate DATE NULL,
ReceiptHour TINYINT NULL,
RequestId INT NOT NULL,
NeutralWaybillNumber VARCHAR(100) NOT NULL,
LabelRetrievedAt_UTC5 DATETIME NULL,
LabelRetrievedDate_UTC5 DATE NULL,
RequestCreatedAt DATETIME NOT NULL,
HasLabel TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (RequestId),
INDEX idx_form_id (FormId),
INDEX idx_waybill (NeutralWaybillNumber),
INDEX idx_receipt_date (ReceiptDate),
INDEX idx_label_date (LabelRetrievedDate_UTC5)
);
INSERT INTO tmp_form_order
SELECT
COALESCE(f_m.Id, f_b.Id) AS FormId,
COALESCE(f_m.HandoverNumber, f_b.HandoverNumber) AS HandoverNumber,
COALESCE(f_m.ReceiptTime, f_b.ReceiptTime) AS ReceiptTime,
DATE(COALESCE(f_m.ReceiptTime, f_b.ReceiptTime)) AS ReceiptDate,
HOUR(COALESCE(f_m.ReceiptTime, f_b.ReceiptTime)) AS ReceiptHour,
r.Id AS RequestId,
r.NeutralWaybillNumber,
CASE WHEN r.LabelRetrievedAt IS NOT NULL
THEN CONVERT_TZ(r.LabelRetrievedAt, '+00:00', '-05:00') END AS LabelRetrievedAt_UTC5,
DATE(CONVERT_TZ(r.LabelRetrievedAt, '+00:00', '-05:00')) AS LabelRetrievedDate_UTC5,
r.CreatedAt AS RequestCreatedAt,
CASE WHEN r.Label IS NOT NULL AND r.Label != '' THEN 1 ELSE 0 END AS HasLabel
FROM label_replace_requests r
LEFT JOIN arrival_handover_forms f_m ON r.MasterPackageNumber = f_m.HandoverNumber
LEFT JOIN arrival_handover_forms f_b ON r.BillOfLadingNumber = f_b.HandoverNumber
WHERE COALESCE(f_m.Id, f_b.Id) IS NOT NULL;
-- ================================================================
-- 步骤3tmp_form_first_scan - 每个交接单的首次扫描时间
-- 直接读取 tmp_scan_agg无需再扫 label_scan_history
-- ================================================================
CREATE TEMPORARY TABLE tmp_form_first_scan (
FormId INT NOT NULL,
FirstScanTime_UTC5 DATETIME NULL,
PRIMARY KEY (FormId)
);
INSERT INTO tmp_form_first_scan
SELECT
fo.FormId,
CONVERT_TZ(MIN(sa.MinScanTime), '+00:00', '-05:00')
FROM tmp_form_order fo
LEFT JOIN tmp_scan_agg sa ON fo.NeutralWaybillNumber = sa.NeutralWaybillNumber
GROUP BY fo.FormId;
-- ================================================================
-- 步骤4tmp_form_stats - 每个交接单的总数、达标阈值和首次达标时间
-- ================================================================
CREATE TEMPORARY TABLE tmp_form_stats (
FormId INT NOT NULL,
TotalOrderCount INT NOT NULL DEFAULT 0,
QualifyNeedCount INT NOT NULL DEFAULT 0,
FirstQualifiedTime_UTC5 DATETIME NULL,
PRIMARY KEY (FormId)
);
INSERT INTO tmp_form_stats (FormId, TotalOrderCount, QualifyNeedCount)
SELECT
FormId,
COUNT(DISTINCT RequestId),
CEIL(COUNT(DISTINCT RequestId) * 0.8)
FROM tmp_form_order
GROUP BY FormId;
CREATE TEMPORARY TABLE tmp_form_push_ranked (
FormId INT NOT NULL,
LabelRetrievedAt_UTC5 DATETIME NULL,
PushOrder INT NOT NULL,
QualifyNeedCount INT NOT NULL,
INDEX idx_form_push (FormId, PushOrder)
);
INSERT INTO tmp_form_push_ranked
SELECT
fo.FormId,
fo.LabelRetrievedAt_UTC5,
ROW_NUMBER() OVER (PARTITION BY fo.FormId ORDER BY fo.LabelRetrievedAt_UTC5),
fs.QualifyNeedCount
FROM tmp_form_order fo
INNER JOIN tmp_form_stats fs ON fo.FormId = fs.FormId
WHERE fo.HasLabel = 1 AND fo.LabelRetrievedAt_UTC5 IS NOT NULL;
UPDATE tmp_form_stats fs
INNER JOIN (
SELECT
FormId,
MIN(LabelRetrievedAt_UTC5) AS FirstQualifiedTime_UTC5
FROM tmp_form_push_ranked
WHERE PushOrder >= QualifyNeedCount
GROUP BY FormId
) q ON fs.FormId = q.FormId
SET fs.FirstQualifiedTime_UTC5 = q.FirstQualifiedTime_UTC5;
DROP TEMPORARY TABLE IF EXISTS tmp_form_push_ranked;
-- ================================================================
-- 步骤5tmp_order_full - 每个有标签订单的完整信息(含考核时间)
-- 预先计算 AssessmentBaseTime / AssessmentTime / AssessmentDate
-- 避免后续重复展开 GREATEST() CASE WHEN
-- ================================================================
CREATE TEMPORARY TABLE tmp_order_full (
RequestId INT NOT NULL,
FormId INT NOT NULL,
NeutralWaybillNumber VARCHAR(100) NOT NULL,
HasLabel TINYINT NOT NULL DEFAULT 0,
ReceiptDate DATE NULL,
ReceiptTime DATETIME NULL,
LabelRetrievedDate_UTC5 DATE NULL,
LabelRetrievedAt_UTC5 DATETIME NULL,
RequestCreatedDate_UTC5 DATE NULL,
AssessmentBaseTime DATETIME NULL,
AssessmentBaseDate DATE NULL,
AssessmentTime DATETIME NULL,
AssessmentDate DATE NULL,
FirstSuccessTime_UTC5 DATETIME NULL,
FirstSuccessDate_UTC5 DATE NULL,
IsSuccess TINYINT NOT NULL DEFAULT 0,
IsStopLabel TINYINT NOT NULL DEFAULT 0,
IsCompletedInAssessment TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (RequestId),
INDEX idx_receipt_date (ReceiptDate),
INDEX idx_label_date (LabelRetrievedDate_UTC5),
INDEX idx_success_date (FirstSuccessDate_UTC5),
INDEX idx_assessment_base (AssessmentBaseDate),
INDEX idx_assessment_date (AssessmentDate),
INDEX idx_created_date (RequestCreatedDate_UTC5),
INDEX idx_success_label_date (IsSuccess, LabelRetrievedDate_UTC5, FirstSuccessDate_UTC5)
);
INSERT INTO tmp_order_full
SELECT
fo.RequestId,
fo.FormId,
fo.NeutralWaybillNumber,
fo.HasLabel,
fo.ReceiptDate,
fo.ReceiptTime,
fo.LabelRetrievedDate_UTC5,
fo.LabelRetrievedAt_UTC5,
DATE(CONVERT_TZ(fo.RequestCreatedAt, '+00:00', '-05:00')) AS RequestCreatedDate_UTC5,
GREATEST(
CASE
WHEN ffs.FirstScanTime_UTC5 IS NOT NULL
AND ffs.FirstScanTime_UTC5 < fo.ReceiptTime
THEN ffs.FirstScanTime_UTC5
ELSE fo.ReceiptTime
END,
fs.FirstQualifiedTime_UTC5
) AS AssessmentBaseTime,
DATE(GREATEST(
CASE
WHEN ffs.FirstScanTime_UTC5 IS NOT NULL
AND ffs.FirstScanTime_UTC5 < fo.ReceiptTime
THEN ffs.FirstScanTime_UTC5
ELSE fo.ReceiptTime
END,
fs.FirstQualifiedTime_UTC5
)) AS AssessmentBaseDate,
CASE
WHEN fs.FirstQualifiedTime_UTC5 IS NOT NULL AND fo.HasLabel = 1
THEN CASE
WHEN HOUR(GREATEST(
CASE
WHEN ffs.FirstScanTime_UTC5 IS NOT NULL
AND ffs.FirstScanTime_UTC5 < fo.ReceiptTime
THEN ffs.FirstScanTime_UTC5
ELSE fo.ReceiptTime
END,
fs.FirstQualifiedTime_UTC5
)) < 16
THEN DATE_ADD(
DATE_ADD(DATE(GREATEST(
CASE
WHEN ffs.FirstScanTime_UTC5 IS NOT NULL
AND ffs.FirstScanTime_UTC5 < fo.ReceiptTime
THEN ffs.FirstScanTime_UTC5
ELSE fo.ReceiptTime
END,
fs.FirstQualifiedTime_UTC5
)), INTERVAL 1 DAY),
INTERVAL 16 HOUR
)
ELSE DATE_ADD(
DATE_ADD(DATE(GREATEST(
CASE
WHEN ffs.FirstScanTime_UTC5 IS NOT NULL
AND ffs.FirstScanTime_UTC5 < fo.ReceiptTime
THEN ffs.FirstScanTime_UTC5
ELSE fo.ReceiptTime
END,
fs.FirstQualifiedTime_UTC5
)), INTERVAL 1 DAY),
INTERVAL '23:59:59' HOUR_SECOND
)
END
ELSE NULL
END AS AssessmentTime,
DATE(CASE
WHEN fs.FirstQualifiedTime_UTC5 IS NOT NULL AND fo.HasLabel = 1
THEN DATE_ADD(DATE(GREATEST(
CASE
WHEN ffs.FirstScanTime_UTC5 IS NOT NULL
AND ffs.FirstScanTime_UTC5 < fo.ReceiptTime
THEN ffs.FirstScanTime_UTC5
ELSE fo.ReceiptTime
END,
fs.FirstQualifiedTime_UTC5
)), INTERVAL 1 DAY)
ELSE NULL
END) AS AssessmentDate,
sa.FirstSuccessTime_UTC5,
sa.FirstSuccessDate_UTC5,
COALESCE(sa.IsSuccess, 0) AS IsSuccess,
COALESCE(sa.IsStopLabel, 0) AS IsStopLabel,
0 AS IsCompletedInAssessment
FROM tmp_form_order fo
INNER JOIN tmp_form_stats fs ON fo.FormId = fs.FormId
LEFT JOIN tmp_form_first_scan ffs ON fo.FormId = ffs.FormId
LEFT JOIN tmp_scan_agg sa ON fo.NeutralWaybillNumber = sa.NeutralWaybillNumber
WHERE fo.HasLabel = 1;
UPDATE tmp_order_full
SET IsCompletedInAssessment = 1
WHERE AssessmentTime IS NOT NULL
AND IsSuccess = 1
AND FirstSuccessTime_UTC5 <= AssessmentTime;
DROP TEMPORARY TABLE IF EXISTS tmp_form_first_scan;
DROP TEMPORARY TABLE IF EXISTS tmp_form_stats;
DROP TEMPORARY TABLE IF EXISTS tmp_form_order;
DROP TEMPORARY TABLE IF EXISTS tmp_scan_agg;
-- ================================================================
-- 步骤6构建 tmp_daily_metrics 的日期维度
-- 从 tmp_order_full 和 tmp_daily_scan 收集所有出现过的日期
-- ================================================================
CREATE TEMPORARY TABLE tmp_daily_metrics (
DATE NOT NULL,
INT NOT NULL DEFAULT 0,
INT NOT NULL DEFAULT 0,
INT NOT NULL DEFAULT 0,
INT NOT NULL DEFAULT 0,
`24H完成数` INT NOT NULL DEFAULT 0,
PRIMARY KEY ()
);
INSERT IGNORE INTO tmp_daily_metrics ()
SELECT DISTINCT ReceiptDate FROM tmp_order_full WHERE ReceiptDate IS NOT NULL
UNION
SELECT DISTINCT LabelRetrievedDate_UTC5 FROM tmp_order_full WHERE LabelRetrievedDate_UTC5 IS NOT NULL
UNION
SELECT DISTINCT FirstSuccessDate_UTC5 FROM tmp_order_full WHERE FirstSuccessDate_UTC5 IS NOT NULL
UNION
SELECT DISTINCT AssessmentBaseDate FROM tmp_order_full WHERE AssessmentBaseDate IS NOT NULL
UNION
SELECT DISTINCT AssessmentDate FROM tmp_order_full WHERE AssessmentDate IS NOT NULL
UNION
SELECT DISTINCT FROM tmp_daily_scan;
-- ================================================================
-- 步骤7按日期聚合各项指标无 CROSS JOIN均为有索引的 GROUP BY
-- ================================================================
-- 当天新增换单数
UPDATE tmp_daily_metrics dm
INNER JOIN (
SELECT ReceiptDate AS , COUNT(DISTINCT RequestId) AS cnt
FROM tmp_order_full
WHERE ReceiptDate IS NOT NULL
GROUP BY ReceiptDate
) t ON dm. = t.
SET dm. = t.cnt;
-- 当日标签推送数
UPDATE tmp_daily_metrics dm
INNER JOIN (
SELECT LabelRetrievedDate_UTC5 AS , COUNT(DISTINCT RequestId) AS cnt
FROM tmp_order_full
WHERE LabelRetrievedDate_UTC5 IS NOT NULL
GROUP BY LabelRetrievedDate_UTC5
) t ON dm. = t.
SET dm. = t.cnt;
-- 当天应该换单数 + 24H完成数
-- 一个订单的 AssessmentBaseDate 和 AssessmentDate 可能不同BaseDate=TAssessmentDate=T+1
-- 该订单在 T 和 T+1 两天都应被计入"当天应该换单数"
-- 使用 UNION 展开两个日期维度后去重统计
CREATE TEMPORARY TABLE tmp_should_replace_dedup (
DATE NOT NULL,
INT NOT NULL DEFAULT 0,
`24H完成数` INT NOT NULL DEFAULT 0,
PRIMARY KEY ()
);
INSERT INTO tmp_should_replace_dedup
SELECT
d.,
COUNT(DISTINCT ofu.RequestId) AS ,
COUNT(DISTINCT CASE
WHEN ofu.IsCompletedInAssessment = 1
AND ofu.FirstSuccessDate_UTC5 = d.
THEN ofu.RequestId END) AS `24H完成数`
FROM (
SELECT DISTINCT AssessmentBaseDate AS FROM tmp_order_full WHERE AssessmentBaseDate IS NOT NULL
UNION
SELECT DISTINCT AssessmentDate AS FROM tmp_order_full WHERE AssessmentDate IS NOT NULL
) d
INNER JOIN tmp_order_full ofu
ON ofu.AssessmentTime IS NOT NULL
AND (ofu.AssessmentBaseDate = d. OR ofu.AssessmentDate = d.)
AND (ofu.IsSuccess = 0 OR ofu.FirstSuccessDate_UTC5 <= d.)
GROUP BY d.;
UPDATE tmp_daily_metrics dm
INNER JOIN tmp_should_replace_dedup srd ON dm. = srd.
SET
dm. = srd.,
dm.`24H完成数` = srd.`24H完成数`;
DROP TEMPORARY TABLE IF EXISTS tmp_should_replace_dedup;
-- 累计要换的总单数
-- 每个订单的计入区间:[MAX(LabelRetrievedDate_UTC5, RequestCreatedDate_UTC5), FirstSuccessDate_UTC5-1 或无穷]
-- 对每个日期 dcount 满足 start_date <= d AND (无成功 OR success_date > d) 的订单数
-- 利用 tmp_daily_metrics × tmp_order_full JOIN但 tmp_order_full 已建索引,
-- 数据量:日期数 × 订单数远小于原来的 CROSS JOINOrderFullInfo 是完整订单,现在只取有标签的)
UPDATE tmp_daily_metrics dm
INNER JOIN (
SELECT
dm2.,
COUNT(DISTINCT ofu.RequestId) AS cnt
FROM tmp_daily_metrics dm2
INNER JOIN tmp_order_full ofu
ON ofu.HasLabel = 1
AND ofu.LabelRetrievedDate_UTC5 <= dm2.
AND ofu.RequestCreatedDate_UTC5 <= dm2.
AND (ofu.IsSuccess = 0 OR ofu.FirstSuccessDate_UTC5 > dm2.)
GROUP BY dm2.
) t ON dm. = t.
SET dm. = t.cnt;
-- ================================================================
-- 最终输出
-- ================================================================
SELECT
dm.,
dm.,
dm.,
dm.,
COALESCE(ds., 0) AS ,
COALESCE(ds., 0) AS ,
COALESCE(ds.STOP数, 0) AS STOP数,
dm.`24H完成数` AS 24,
dm.,
COALESCE(ds., 0) AS ,
CASE
WHEN dm. = 0 THEN '0.00%'
ELSE CONCAT(ROUND(COALESCE(ds., 0) / dm. * 100, 2), '%')
END AS ,
CASE
WHEN dm. = 0 THEN '0.00%'
ELSE CONCAT(ROUND(dm.`24H完成数` / dm. * 100, 2), '%')
END AS 24,
(UTC_TIMESTAMP() - INTERVAL 5 HOUR) AS UTC_5
FROM tmp_daily_metrics dm
LEFT JOIN tmp_daily_scan ds ON dm. = ds.
ORDER BY dm. DESC;
-- ================================================================
-- 清理临时表
-- ================================================================
DROP TEMPORARY TABLE IF EXISTS tmp_daily_scan;
DROP TEMPORARY TABLE IF EXISTS tmp_order_full;
DROP TEMPORARY TABLE IF EXISTS tmp_daily_metrics;
END$$
DELIMITER ;