从LIKE暴力匹配到LLM智能分类——遗留系统数据分析实战

从LIKE暴力匹配到LLM智能分类------遗留系统数据分析实战

文章目录

一、故事的起点

接手一个餐饮老系统的数据分析任务。打开数据库一看,流水表里几万条记录,只有商品名称、数量、单价、金额、日期。没有分类字段,没有品类标签。

老板说:帮我看一下,哪个品类贡献了最多营收?哪个单品卖得最好?

几千个不同的商品名称,人工分类?不现实。当时用的办法,说出来有点笨------用SQL的LIKE逐个品类往里套

二、LIKE暴力分类:笨但能跑

看看当时写的SQL(保留原样,包括那些注释掉的条件):

sql 复制代码
insert into cf_fl
select distinct 
   '海鲜类' fl,
   names
from cf_product a
where names like '%虾%'
  and names not like '%虾片%'
  and names not like '%龙虾味%'

就这?对,就这。

每一类都是一轮"试错→排除→再试错"的循环。注释里密密麻麻全是被推翻的条件:

sql 复制代码
-- names like '%蟹%' and names not like '%蟹肉棒%'
-- names like '%鸡%' and names not like '%鸡蛋%'
-- names like '%牛%' and names not like '%牛油%' and names not like '%牛奶%'

为什么这么多排除条件?因为中餐菜名太坑了:

  • "蚂蚁上树"------没有蚂蚁,是肉末粉条(猪肉类)
  • "夫妻肺片"------没有夫妻,是牛肉牛杂(牛肉类)
  • "鱼香肉丝"------没有鱼,是猪肉类
  • "狮子头"------没有狮子,是猪肉类

LIKE只能做字面匹配,理解不了语义。每遇到一个误分类,就得加一个排除条件。几千条数据,光分类就调了两三天。

最终效果是这样的:逐条INSERT进分类表(cf_fl),两个字段------类别和商品名称。一个品类一个品类地攒出来的。

sql 复制代码
-- 查某个品类的销售情况
select a.names,
       count(*) as 销售次数,
       sum(numbers) as 销售总量,
       sum(costs)   as 总营收,
       max(price)   as 最高单价,
       min(price)   as 最低单价,
       round(sum(costs) / sum(numbers), 2) as 客单价
  from cf_product a, cf_fl b
 where a.names = b.names
   and b.fl = '海鲜类'
 group by a.names
 order by 4 desc;

跑出来的结果很有意思:同一个菜,最高单价和最低单价能差出好几倍。比如一道"蒜蓉大虾",最高卖过128,最低卖过68。这不是折扣,是定价混乱------不同时段、不同服务员可能录入的价格不一样。这种问题,不分析数据根本发现不了。

三、包厢利用率:把时间轴展开

分类问题解决了,接下来是包厢分析。

数据长这样:

tablename(包厢号) btime(开台时间) etime(结账时间) billid(账单号)
301 2022-01-08 18:30 2022-01-08 22:15 XS-220108-0023
302 2022-01-08 19:00 2022-01-09 01:30 XS-220108-0025

老板想知道:每个包厢,每天每个小时,是空闲还是占用?

原始数据只有开台和结账时间,要变成"每小时的状态",得自己算。当时的做法------PL/SQL暴力遍历:

sql 复制代码
declare
  n_count number;
  ctime   date;
  date1   date;
  hour1   number := 0;
begin
  date1 := to_date('20220101', 'yyyymmdd');
  while (date1 < to_date('20220401', 'yyyymmdd')) loop
    for rec in (select * from bx) loop
      hour1 := 0;
      while (hour1 < 24) loop
        ctime := date1 + hour1 / 24;
        select count(*)
          into n_count
          from bxlog
         where tablename = rec.tablename
           and ctime between btime and etime;
        if n_count > 0 then
          insert into bxrq (rq, tablename, chour, kyzt)
          values (date1, rec.tablename, hour1, '有客');
        else
          insert into bxrq (rq, tablename, chour, kyzt)
          values (date1, rec.tablename, hour1, '无客');
        end if;
        hour1 := hour1 + 1;
        commit;
      end loop;
    end loop;
    date1 := date1 + 1;
  end loop;
end;

三个嵌套循环:90天 × 十几间包厢 × 24小时 = 三万多条记录。跑了好几分钟才出结果。

然后用Oracle的PIVOT展开成矩阵:

sql 复制代码
select *
  from (select tablename, rq, chour,
               decode(kyzt, '无客', 1, 0) ky
          from BXRQ)
pivot(sum(ky) for chour in (
  '0','1','2','3','4','5','6','7','8','9',
  '10','11','12','13','14','15','16','17',
  '18','19','20','21','22','23'))

1表示空闲,0表示占用。一张热力图就出来了。

再按星期汇总:

sql 复制代码
select 
  decode(to_char(rq, 'd'),
    '7','星期六','1','星期日','2','星期一',
    '3','星期二','4','星期三','5','星期四','6','星期五') as 星期,
  count(distinct rq) as 总天数,
  sum(case when hour22=1 and hour23=1 and hour0=1 then 1 else 0 end) 
    as "晚10点后全满天数"
from bxky
group by decode(to_char(rq, 'd'),
    '7','星期六','1','星期日','2','星期一',
    '3','星期二','4','星期三','5','星期四','6','星期五')
order by 3;

结论很清晰:周六晚上10点到凌晨,几乎所有包厢都满员。周一到周五的下午,大面积空闲。

这不是什么高深的洞察,但它是真实的。不做数据分析,老板只能凭感觉说"周末忙、平时闲",但到底多忙、多闲,哪个时段最空,不知道。有了数据,决策才有依据------空闲时段推低价套餐引流,高峰时段提高最低消费门槛。

四、如果现在重做:用LLM做分类

时隔几年再看这些SQL,最想改的就是分类那块。

LIKE暴力匹配的痛点很明确:

  1. 维护成本高。每新增一个菜品,都要手动判断归哪类。新品上架了,分类表没更新,分析就漏了
  2. 误分类多。"鱼香肉丝"含"鱼"字但不是鱼类,"鸡蛋灌饼"含"鸡"字但不是禽类。排除条件越加越多,越来越难维护
  3. 无法泛化。换一个餐厅,菜名体系完全不同,所有条件得重写

现在用LLM,事情简单得多。

4.1 不需要7B,3B就够了

菜品分类是短文本分类任务:输入一个菜名(通常2-10个字),输出一个类别标签。这在NLP领域是最基础的任务之一。

方案 适用场景 本任务是否需要
BERT/TextCNN 有标注数据,需训练 够用,但要标注
LLM 1.5B-3B 少样本,零训练 最推荐
LLM 7B 更复杂的推理 杀鸡用牛刀
LLM 70B+ 通用任务 完全没必要

用Qwen2.5-3B甚至更小的模型,本地部署,几行代码搞定:

python 复制代码
import json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8080/v1", api_key="empty")

categories = ["海鲜类", "牛肉类", "猪肉类", "禽类", "蔬菜类", "主食类", "酒水类", "饮品甜品类", "其他"]

def classify_dish(name):
    resp = client.chat.completions.create(
        model="qwen2.5-3b",
        messages=[
            {"role": "system", "content": f"你是餐饮菜品分类助手。将菜名归入以下类别之一:{', '.join(categories)}。只输出类别名称,不要解释。"},
            {"role": "user", "content": f"菜名:{name}"}
        ],
        temperature=0
    )
    return resp.choices[0].message.content.strip()

dishes = ["蒜蓉大虾", "蚂蚁上树", "夫妻肺片", "鱼香肉丝", "狮子头", "青岛啤酒", "芒果布丁"]
for d in dishes:
    print(f"{d} -> {classify_dish(d)}")

预期输出:

复制代码
蒜蓉大虾 -> 海鲜类
蚂蚁上树 -> 猪肉类
夫妻肺片 -> 牛肉类
鱼香肉丝 -> 猪肉类
狮子头 -> 猪肉类
青岛啤酒 -> 酒水类
芒果布丁 -> 饮品甜品类

LIKE做不到的事,LLM轻松搞定。"蚂蚁上树"它知道是肉末粉条,不是虫类;"鱼香肉丝"它知道是川菜猪肉,不是鱼类。这是语义理解,不是字面匹配。

4.2 处理边界情况

当然,LLM也不是万能的。遇到本地特色菜、自创菜名,可能也会犯错。比如"九星招牌虾"------如果"九星"是店名,这应该归海鲜类,但LLM可能不确定。

解决方法:少样本提示(Few-shot)。在prompt里给几个例子:

python 复制代码
messages = [
    {"role": "system", "content": f"你是餐饮菜品分类助手。将菜名归入以下类别之一:{', '.join(categories)}。只输出类别名称。\n\n示例:\n九星招牌虾 -> 海鲜类\n秘制烤羊排 -> 牛羊肉类\n特色素三鲜 -> 蔬菜类"},
    {"role": "user", "content": f"菜名:{name}"}
]

加上几个本店特色菜的例子,准确率能从85%拉到95%以上。剩下5%的异常值,人工过一遍就行。

4.3 效率对比

维度 LIKE暴力匹配 LLM分类
开发时间 2-3天(反复调条件) 半天(写prompt+跑批)
新增菜品 手动加条件 自动分类
误分类率 10%-15%(语义陷阱) < 5%(加few-shot后)
可迁移性 换店重写 改几个few-shot例子
维护成本 持续累积排除条件 基本无需维护

几千条数据,本地3B模型跑完不到半小时。加上人工抽查,整个过程半天搞定。当年用LIKE,光"虾"这个关键字就调了半天。

五、教训与反思

回头看这次数据分析,最大的教训不是技术选型,而是:遗留系统的数据质量,决定了分析的上限。

  • 菜名不统一:同一个菜,有的写"蒜蓉虾",有的写"蒜蓉大虾",有的写"蒜蓉开背虾"。LIKE和LLM都会把它们当成不同的菜
  • 价格混乱:同菜不同价,可能是时段定价,也可能是录错了
  • 包厢数据有脏数据:有些记录的开台时间晚于结账时间,PL/SQL遍历时会漏算

这些问题不是SQL或LLM能解决的,需要在数据采集端就做好规范化。

第二个体会是:数据分析的价值不在于技术多酷,而在于能不能回答业务问题。

包厢分析那段PL/SQL,写法很丑------三层嵌套循环,逐行INSERT,跑好几分钟。但跑出来的那张热力图,直接告诉老板"周六晚10点以后一房难求,周三下午全空"。这个结论值多少钱?如果据此调整定价策略,一个月多出来的营收可能覆盖整个数据分析的成本。

LIKE分类也很丑。但当年没有更好的工具,它解决了问题。现在有了LLM,同样的任务可以用更优雅的方式做。技术的进步就在于此:不是让不可能变成可能,而是让可能的成本从三天降到半天。

六、如果现在从头做

我会这样设计:

  1. 分类:LLM(3B本地模型),自动分类 + 人工抽查异常值
  2. 包厢分析:不再用PL/SQL暴力遍历,改用SQL窗口函数或Python pandas处理时间区间
  3. 可视化:包厢热力图直接用Python(matplotlib/seaborn),不用在数据库里PIVOT
  4. 数据清洗:先用LLM做菜名标准化("蒜蓉虾"和"蒜蓉大虾"合并为同一道菜),再分类

技术栈从"Oracle + 纯SQL"变成"Python + LLM + 可视化工具",但分析思路是一样的:先解决分类问题,再做聚合统计,最后回答业务问题。

工具换了,方法论没变。这大概就是数据分析的本质。

相关推荐
纸鸢|5 小时前
边缘计算+AI:设备振动分析与故障诊断技术实践
大数据·人工智能
瑞华丽PLM5 小时前
国产PLM软件供应商
大数据·人工智能·国产plm·瑞华丽plm·瑞华丽
初心未改HD5 小时前
NLP之GPT生成式模型详解
人工智能·自然语言处理
AI品信智慧数智人5 小时前
当智能语音交互遇上仿真机器人,解锁AI人机交互新范式✨
人工智能·机器人·交互
jimmyleeee5 小时前
人工智能基础知识笔记四十:Claude 扩展机制深度解构:Command、Skill、Sub-agent 与 Hook 的四层协同架构
人工智能·笔记
xingyuzhisuan5 小时前
2026实测:租用RTX 4090 CUDA适配与PyTorch精准安装教程
人工智能·pytorch·python·深度学习·gpu算力
逆境不可逃5 小时前
【与我学 ClaudeCode】规划与协调篇 之 Skills:按需加载的领域知识框架
大数据·人工智能·elasticsearch·搜索引擎·agent·claudecode
嗯、.6 小时前
Agent 路由架构的一次尝试:LangGraph + Swarm Handoff + 小模型 Router
人工智能·python·swarm·langgraph·multi-agent·model-routing
luyu007_0076 小时前
鲁渝能源全功率无线充电为巡检机器人筑牢能源底座
人工智能·安全·机器人·能源