USE [JSDataMiddlePlatform]; SET NOCOUNT ON; DECLARE @start_date date = DATEADD(day, -29, CAST(GETDATE() AS date)); DECLARE @end_date date = CAST(GETDATE() AS date); DECLARE @start_time datetime2 = CAST(@start_date AS datetime2); DECLARE @end_time datetime2 = DATEADD(day, 1, CAST(@end_date AS datetime2)); DECLARE @shop nvarchar(100) = N''; -- 可填店铺关键词,例如 N'亿家达' DECLARE @limit int = 20; IF OBJECT_ID('tempdb..#chat_base') IS NOT NULL DROP TABLE #chat_base; SELECT c.id, NULLIF(c.orderNo, '') AS order_no, ISNULL(NULLIF(c.shopName, ''), N'未知店铺') AS shop_name, c.createdAt, c.updatedAt, c.state, ISNULL(NULLIF(LTRIM(RTRIM(c.mainReason)), ''), N'未标注') AS main_reason, ISNULL(NULLIF(LTRIM(RTRIM(c.secondReason)), ''), N'未标注') AS second_reason, ISNULL(NULLIF(LTRIM(RTRIM(c.thirdReason)), ''), N'未标注') AS third_reason, CAST(c.content AS nvarchar(max)) AS content_text, CAST(c.reasonContent AS nvarchar(max)) AS reason_text INTO #chat_base FROM dbo.pc_kfChatLog AS c WITH (NOLOCK) WHERE c.createdAt >= @start_time AND c.createdAt < @end_time AND (@shop = N'' OR c.shopName LIKE N'%' + @shop + N'%'); IF OBJECT_ID('tempdb..#chat_norm') IS NOT NULL DROP TABLE #chat_norm; SELECT b.*, CASE WHEN b.content_text IS NULL OR LTRIM(RTRIM(b.content_text)) IN (N'', N'[]') THEN 1 ELSE 0 END AS is_empty_content, CASE WHEN b.main_reason = N'未标注' OR b.second_reason = N'未标注' THEN 1 ELSE 0 END AS is_missing_reason, CASE WHEN b.main_reason = N'无对应分类' OR b.second_reason = N'无对应分类' THEN 1 ELSE 0 END AS is_no_match_reason, CASE WHEN b.content_text LIKE N'%欢迎%' OR b.content_text LIKE N'%您好%' OR b.content_text LIKE N'%亲,%' OR b.content_text LIKE N'%亲,%' THEN 1 ELSE 0 END AS has_greeting, CASE WHEN b.content_text LIKE N'%抱歉%' OR b.content_text LIKE N'%不好意思%' OR b.content_text LIKE N'%理解您%' THEN 1 ELSE 0 END AS has_empathy, CASE WHEN b.content_text LIKE N'%马上%' OR b.content_text LIKE N'%尽快%' OR b.content_text LIKE N'%给您处理%' OR b.content_text LIKE N'%为您处理%' THEN 1 ELSE 0 END AS has_commitment, CASE WHEN b.content_text LIKE N'%感谢%' OR b.content_text LIKE N'%祝您%' OR b.content_text LIKE N'%辛苦%' THEN 1 ELSE 0 END AS has_closing, CASE WHEN b.content_text LIKE N'%不知道%' OR b.content_text LIKE N'%没办法%' OR b.content_text LIKE N'%无法处理%' OR b.content_text LIKE N'%不清楚%' OR b.content_text LIKE N'%等不了%' OR b.content_text LIKE N'%太慢%' THEN 1 ELSE 0 END AS has_risk_phrase INTO #chat_norm FROM #chat_base AS b; -- 1) 时间段客服体验总览 SELECT @start_date AS start_date, @end_date AS end_date, COUNT(*) AS session_count, COUNT(DISTINCT order_no) AS order_count, COUNT(DISTINCT shop_name) AS shop_count, SUM(is_empty_content) AS empty_content_count, CAST(100.0 * SUM(is_empty_content) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS empty_content_rate_pct, SUM(is_missing_reason) AS missing_reason_count, CAST(100.0 * SUM(is_missing_reason) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS missing_reason_rate_pct, SUM(is_no_match_reason) AS no_match_reason_count, CAST(100.0 * SUM(is_no_match_reason) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS no_match_reason_rate_pct, SUM(CASE WHEN state = 1 THEN 1 ELSE 0 END) AS state_1_count, SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END) AS state_2_count FROM #chat_norm; -- 2) 用户反馈问题结构 TOP(一级/二级) SELECT TOP (@limit) main_reason, second_reason, COUNT(*) AS session_count, COUNT(DISTINCT order_no) AS order_count, CAST(100.0 * COUNT(*) / NULLIF((SELECT COUNT(*) FROM #chat_norm), 0) AS decimal(6,2)) AS session_share_pct FROM #chat_norm GROUP BY main_reason, second_reason ORDER BY session_count DESC, order_count DESC; -- 3) 客服体验店铺对比(体验分) WITH shop_agg AS ( SELECT shop_name, COUNT(*) AS session_count, SUM(is_empty_content) AS empty_content_count, SUM(is_missing_reason) AS missing_reason_count, SUM(is_no_match_reason) AS no_match_reason_count FROM #chat_norm GROUP BY shop_name ) SELECT TOP (@limit) s.shop_name, s.session_count, CAST(100.0 * s.empty_content_count / NULLIF(s.session_count, 0) AS decimal(6,2)) AS empty_content_rate_pct, CAST(100.0 * s.missing_reason_count / NULLIF(s.session_count, 0) AS decimal(6,2)) AS missing_reason_rate_pct, CAST(100.0 * s.no_match_reason_count / NULLIF(s.session_count, 0) AS decimal(6,2)) AS no_match_reason_rate_pct, CASE WHEN ( 100.0 - (100.0 * s.missing_reason_count / NULLIF(s.session_count, 0)) * 0.4 - (100.0 * s.no_match_reason_count / NULLIF(s.session_count, 0)) * 0.4 - (100.0 * s.empty_content_count / NULLIF(s.session_count, 0)) * 0.2 ) < 0 THEN CAST(0 AS decimal(6,2)) ELSE CAST( 100.0 - (100.0 * s.missing_reason_count / NULLIF(s.session_count, 0)) * 0.4 - (100.0 * s.no_match_reason_count / NULLIF(s.session_count, 0)) * 0.4 - (100.0 * s.empty_content_count / NULLIF(s.session_count, 0)) * 0.2 AS decimal(6,2) ) END AS experience_score FROM shop_agg AS s ORDER BY experience_score DESC, s.session_count DESC; -- 4) 话术覆盖分析 SELECT COUNT(*) AS session_count, SUM(has_greeting) AS greeting_hit_count, CAST(100.0 * SUM(has_greeting) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS greeting_hit_rate_pct, SUM(has_empathy) AS empathy_hit_count, CAST(100.0 * SUM(has_empathy) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS empathy_hit_rate_pct, SUM(has_commitment) AS commitment_hit_count, CAST(100.0 * SUM(has_commitment) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS commitment_hit_rate_pct, SUM(has_closing) AS closing_hit_count, CAST(100.0 * SUM(has_closing) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS closing_hit_rate_pct, SUM(has_risk_phrase) AS risk_phrase_hit_count, CAST(100.0 * SUM(has_risk_phrase) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS risk_phrase_hit_rate_pct FROM #chat_norm; -- 5) 风险话术命中 TOP WITH phrase_hits AS ( SELECT n.id, n.order_no, n.shop_name, v.risk_phrase FROM #chat_norm AS n CROSS APPLY (VALUES (CASE WHEN n.content_text LIKE N'%不知道%' THEN N'不知道' END), (CASE WHEN n.content_text LIKE N'%没办法%' THEN N'没办法' END), (CASE WHEN n.content_text LIKE N'%无法处理%' THEN N'无法处理' END), (CASE WHEN n.content_text LIKE N'%不清楚%' THEN N'不清楚' END), (CASE WHEN n.content_text LIKE N'%太慢%' THEN N'太慢' END), (CASE WHEN n.content_text LIKE N'%等不了%' THEN N'等不了' END) ) AS v(risk_phrase) WHERE v.risk_phrase IS NOT NULL ) SELECT TOP (@limit) risk_phrase, COUNT(*) AS hit_count, COUNT(DISTINCT id) AS session_count, COUNT(DISTINCT order_no) AS order_count FROM phrase_hits GROUP BY risk_phrase ORDER BY session_count DESC, hit_count DESC; -- 6) 每日体验趋势 SELECT CONVERT(date, createdAt) AS stat_date, COUNT(*) AS session_count, CAST(100.0 * SUM(is_no_match_reason) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS no_match_reason_rate_pct, CAST(100.0 * SUM(is_missing_reason) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS missing_reason_rate_pct, CAST(100.0 * SUM(is_empty_content) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS empty_content_rate_pct, CAST(100.0 * SUM(has_risk_phrase) / NULLIF(COUNT(*), 0) AS decimal(6,2)) AS risk_phrase_hit_rate_pct FROM #chat_norm GROUP BY CONVERT(date, createdAt) ORDER BY stat_date ASC; -- 7) 对话案例(风险话术)+ 推荐回复 WITH case_pool AS ( SELECT n.id, n.order_no, n.shop_name, n.main_reason, n.second_reason, n.content_text, CASE WHEN n.content_text LIKE N'%不清楚%' THEN N'不清楚' WHEN n.content_text LIKE N'%不知道%' THEN N'不知道' WHEN n.content_text LIKE N'%没办法%' THEN N'没办法' WHEN n.content_text LIKE N'%太慢%' THEN N'太慢' WHEN n.content_text LIKE N'%等不了%' THEN N'等不了' WHEN n.content_text LIKE N'%无法处理%' THEN N'无法处理' ELSE NULL END AS risk_tag FROM #chat_norm AS n ) SELECT TOP (@limit) order_no, shop_name, main_reason, second_reason, risk_tag, LEFT(REPLACE(REPLACE(ISNULL(content_text, N''), CHAR(13), N' '), CHAR(10), N' '), 220) AS dialogue_sample, CASE WHEN risk_tag IN (N'不清楚', N'不知道') THEN N'建议回复:我先帮您核实一下该问题,预计5分钟内给您明确答复。' WHEN risk_tag = N'没办法' THEN N'建议回复:这个情况我来帮您申请处理方案,先给您两个可行选项并同步时效。' WHEN risk_tag = N'无法处理' THEN N'建议回复:我已升级给对应专员处理,预计今天内给您处理结果。' WHEN risk_tag IN (N'太慢', N'等不了') THEN N'建议回复:抱歉让您久等,我现在优先为您加急处理,并在10分钟内回报进展。' ELSE N'建议回复:收到您的问题,我先确认订单与处理路径,稍后给您明确方案。' END AS suggested_reply FROM case_pool WHERE risk_tag IS NOT NULL ORDER BY id DESC; -- 8) 客服能力评分(参考天猫服务保障维度,按可用字段做代理映射) ;WITH score_base AS ( SELECT shop_name, COUNT(*) AS session_count, CAST(SUM(has_commitment) AS float) / NULLIF(COUNT(*), 0) AS commitment_rate, CAST(SUM(has_empathy) AS float) / NULLIF(COUNT(*), 0) AS empathy_rate, CAST(SUM(has_closing) AS float) / NULLIF(COUNT(*), 0) AS closing_rate, CAST(SUM(has_risk_phrase) AS float) / NULLIF(COUNT(*), 0) AS risk_phrase_rate, CAST(SUM(CASE WHEN second_reason LIKE N'%催退款%' OR second_reason LIKE N'%催取件%' THEN 1 ELSE 0 END) AS float) / NULLIF(COUNT(*), 0) AS refund_urge_rate, CAST(SUM(is_no_match_reason) AS float) / NULLIF(COUNT(*), 0) AS no_match_rate, CAST(SUM(is_missing_reason) AS float) / NULLIF(COUNT(*), 0) AS missing_reason_rate FROM #chat_norm GROUP BY shop_name ), score_calc AS ( SELECT sb.*, 100.0 * (0.70 * sb.commitment_rate + 0.30 * (1.0 - sb.risk_phrase_rate)) AS resp_ability_score, 100.0 * (0.45 * sb.empathy_rate + 0.35 * sb.closing_rate + 0.20 * (1.0 - sb.risk_phrase_rate)) AS satisfaction_score, 100.0 * (1.0 - sb.refund_urge_rate) AS refund_efficiency_score, 100.0 * (1.0 - (0.80 * sb.no_match_rate + 0.20 * sb.missing_reason_rate)) AS help_risk_control_score FROM score_base AS sb ) SELECT TOP (@limit) shop_name, session_count, CAST(resp_ability_score AS decimal(6,2)) AS resp_ability_score, CAST(satisfaction_score AS decimal(6,2)) AS satisfaction_score, CAST(refund_efficiency_score AS decimal(6,2)) AS refund_efficiency_score, CAST(help_risk_control_score AS decimal(6,2)) AS help_risk_control_score, CAST( 0.30 * resp_ability_score + 0.30 * satisfaction_score + 0.20 * refund_efficiency_score + 0.20 * help_risk_control_score AS decimal(6,2) ) AS service_capability_score, CASE WHEN ( 0.30 * resp_ability_score + 0.30 * satisfaction_score + 0.20 * refund_efficiency_score + 0.20 * help_risk_control_score ) >= 90 THEN N'S' WHEN ( 0.30 * resp_ability_score + 0.30 * satisfaction_score + 0.20 * refund_efficiency_score + 0.20 * help_risk_control_score ) >= 80 THEN N'A' WHEN ( 0.30 * resp_ability_score + 0.30 * satisfaction_score + 0.20 * refund_efficiency_score + 0.20 * help_risk_control_score ) >= 70 THEN N'B' WHEN ( 0.30 * resp_ability_score + 0.30 * satisfaction_score + 0.20 * refund_efficiency_score + 0.20 * help_risk_control_score ) >= 60 THEN N'C' ELSE N'D' END AS service_grade, N'映射说明: 响应能力≈旺旺响应能力; 满意度≈旺旺满意度; 退款效率≈退款处理体验; 求助风险控制≈平台求助风险(代理口径)' AS score_mapping_note FROM score_calc ORDER BY service_capability_score DESC, session_count DESC;