从LIKE暴力匹配到LLM智能分类——遗留系统数据分析实战文章目录从LIKE暴力匹配到LLM智能分类——遗留系统数据分析实战一、故事的起点二、LIKE暴力分类笨但能跑三、包厢利用率把时间轴展开四、如果现在重做用LLM做分类4.1 不需要7B3B就够了4.2 处理边界情况4.3 效率对比五、教训与反思六、如果现在从头做一、故事的起点接手一个餐饮老系统的数据分析任务。打开数据库一看流水表里几万条记录只有商品名称、数量、单价、金额、日期。没有分类字段没有品类标签。老板说帮我看一下哪个品类贡献了最多营收哪个单品卖得最好几千个不同的商品名称人工分类不现实。当时用的办法说出来有点笨——用SQL的LIKE逐个品类往里套。二、LIKE暴力分类笨但能跑看看当时写的SQL保留原样包括那些注释掉的条件insertintocf_flselectdistinct海鲜类fl,namesfromcf_product awherenameslike%虾%andnamesnotlike%虾片%andnamesnotlike%龙虾味%就这对就这。每一类都是一轮试错→排除→再试错的循环。注释里密密麻麻全是被推翻的条件-- 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两个字段——类别和商品名称。一个品类一个品类地攒出来的。-- 查某个品类的销售情况selecta.names,count(*)as销售次数,sum(numbers)as销售总量,sum(costs)as总营收,max(price)as最高单价,min(price)as最低单价,round(sum(costs)/sum(numbers),2)as客单价fromcf_product a,cf_fl bwherea.namesb.namesandb.fl海鲜类groupbya.namesorderby4desc;跑出来的结果很有意思同一个菜最高单价和最低单价能差出好几倍。比如一道蒜蓉大虾最高卖过128最低卖过68。这不是折扣是定价混乱——不同时段、不同服务员可能录入的价格不一样。这种问题不分析数据根本发现不了。三、包厢利用率把时间轴展开分类问题解决了接下来是包厢分析。数据长这样tablename包厢号btime开台时间etime结账时间billid账单号3012022-01-08 18:302022-01-08 22:15XS-220108-00233022022-01-08 19:002022-01-09 01:30XS-220108-0025老板想知道每个包厢每天每个小时是空闲还是占用原始数据只有开台和结账时间要变成每小时的状态得自己算。当时的做法——PL/SQL暴力遍历declaren_count number;ctimedate;date1date;hour1 number :0;begindate1 :to_date(20220101,yyyymmdd);while(date1to_date(20220401,yyyymmdd))loopforrecin(select*frombx)loophour1 :0;while(hour124)loopctime :date1hour1/24;selectcount(*)inton_countfrombxlogwheretablenamerec.tablenameandctimebetweenbtimeandetime;ifn_count0theninsertintobxrq(rq,tablename,chour,kyzt)values(date1,rec.tablename,hour1,有客);elseinsertintobxrq(rq,tablename,chour,kyzt)values(date1,rec.tablename,hour1,无客);endif;hour1 :hour11;commit;endloop;endloop;date1 :date11;endloop;end;三个嵌套循环90天 × 十几间包厢 × 24小时 三万多条记录。跑了好几分钟才出结果。然后用Oracle的PIVOT展开成矩阵select*from(selecttablename,rq,chour,decode(kyzt,无客,1,0)kyfromBXRQ)pivot(sum(ky)forchourin(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表示占用。一张热力图就出来了。再按星期汇总selectdecode(to_char(rq,d),7,星期六,1,星期日,2,星期一,3,星期二,4,星期三,5,星期四,6,星期五)as星期,count(distinctrq)as总天数,sum(casewhenhour221andhour231andhour01then1else0end)as晚10点后全满天数frombxkygroupbydecode(to_char(rq,d),7,星期六,1,星期日,2,星期一,3,星期二,4,星期三,5,星期四,6,星期五)orderby3;结论很清晰周六晚上10点到凌晨几乎所有包厢都满员。周一到周五的下午大面积空闲。这不是什么高深的洞察但它是真实的。不做数据分析老板只能凭感觉说周末忙、平时闲但到底多忙、多闲哪个时段最空不知道。有了数据决策才有依据——空闲时段推低价套餐引流高峰时段提高最低消费门槛。四、如果现在重做用LLM做分类时隔几年再看这些SQL最想改的就是分类那块。LIKE暴力匹配的痛点很明确维护成本高。每新增一个菜品都要手动判断归哪类。新品上架了分类表没更新分析就漏了误分类多。鱼香肉丝含鱼字但不是鱼类鸡蛋灌饼含鸡字但不是禽类。排除条件越加越多越来越难维护无法泛化。换一个餐厅菜名体系完全不同所有条件得重写现在用LLM事情简单得多。4.1 不需要7B3B就够了菜品分类是短文本分类任务输入一个菜名通常2-10个字输出一个类别标签。这在NLP领域是最基础的任务之一。方案适用场景本任务是否需要BERT/TextCNN有标注数据需训练够用但要标注LLM 1.5B-3B少样本零训练最推荐LLM 7B更复杂的推理杀鸡用牛刀LLM 70B通用任务完全没必要用Qwen2.5-3B甚至更小的模型本地部署几行代码搞定importjsonfromopenaiimportOpenAI clientOpenAI(base_urlhttp://localhost:8080/v1,api_keyempty)categories[海鲜类,牛肉类,猪肉类,禽类,蔬菜类,主食类,酒水类,饮品甜品类,其他]defclassify_dish(name):respclient.chat.completions.create(modelqwen2.5-3b,messages[{role:system,content:f你是餐饮菜品分类助手。将菜名归入以下类别之一{, .join(categories)}。只输出类别名称不要解释。},{role:user,content:f菜名{name}}],temperature0)returnresp.choices[0].message.content.strip()dishes[蒜蓉大虾,蚂蚁上树,夫妻肺片,鱼香肉丝,狮子头,青岛啤酒,芒果布丁]fordindishes:print(f{d}-{classify_dish(d)})预期输出蒜蓉大虾 - 海鲜类 蚂蚁上树 - 猪肉类 夫妻肺片 - 牛肉类 鱼香肉丝 - 猪肉类 狮子头 - 猪肉类 青岛啤酒 - 酒水类 芒果布丁 - 饮品甜品类LIKE做不到的事LLM轻松搞定。蚂蚁上树它知道是肉末粉条不是虫类鱼香肉丝它知道是川菜猪肉不是鱼类。这是语义理解不是字面匹配。4.2 处理边界情况当然LLM也不是万能的。遇到本地特色菜、自创菜名可能也会犯错。比如九星招牌虾——如果九星是店名这应该归海鲜类但LLM可能不确定。解决方法少样本提示Few-shot。在prompt里给几个例子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同样的任务可以用更优雅的方式做。技术的进步就在于此不是让不可能变成可能而是让可能的成本从三天降到半天。六、如果现在从头做我会这样设计分类LLM3B本地模型自动分类 人工抽查异常值包厢分析不再用PL/SQL暴力遍历改用SQL窗口函数或Python pandas处理时间区间可视化包厢热力图直接用Pythonmatplotlib/seaborn不用在数据库里PIVOT数据清洗先用LLM做菜名标准化蒜蓉虾和蒜蓉大虾合并为同一道菜再分类技术栈从Oracle 纯SQL变成Python LLM 可视化工具但分析思路是一样的先解决分类问题再做聚合统计最后回答业务问题。工具换了方法论没变。这大概就是数据分析的本质。