一段SQL,推动12306上线“车内换座”功能

梁敬彬 2024-09-26 11:07:41
作者介绍

梁敬彬,担任过福富研究院副理事长、中国电信数据库专家、中盾安信研究院副院长、宁德时代数据专家等职位,著有《收获,不止Oracle》《收获,不止SQL优化》等多本畅销技术书籍,新书《超融合数据库》即将出版。本文内容来源其个人公众号:收获不止数据库(ID:ldbast)

 

一、故事从进京买车票说起

 

一个月前,笔者准备去北京参加技术大会。于是预订了从福州到北京的G302次高铁票,后来临时有事,尝试将G302(7:23出发)改签到G322(9:14出发)。

 

 

结果发现系统提示G322"售罄",无法完成改签。

 

 

二、分段分配新座的需求

 

接下来我们做一个需求分析。

 

为便于分析,我们把福州到北京的路线简化为只分四段,路线:福州 → 杭州 → 南京 → 北京,同时把车厢也限制在仅有10号车厢。

 

心有不甘的笔者经过查询,发现了一些玄机:

 

  • 10车13F:福州至杭州空闲,杭州至南京已售,南京至北京空闲

  • 10车07C:福州至杭州已售,杭州至南京空闲,南京至北京空闲

 

发现玄机了,实际上是有可行的座位组合的:

 

  • 福州到杭州坐10车13F

  • 杭州到南京换到10车07C

  • 南京到北京可以回到10车13F或继续坐10车07C

 

这意味着,如果系统支持分段分配新座位,是可以完成全程订票的。然而,现有系统只能查询和分配全程相同的座位,无法识别这种分段组合的可能性。于是笔者放弃了,继续乘坐原来的G302(7:23出发)。

 

当然了,理论上笔者是可以买一张福州到杭州的车票,再购买杭州到北京的车,不过觉得麻烦,也就算了。其实如果12306允许自动给乘客分段分配座位(即到不同的站换不同的座位),问题就可以解决。那该怎么实现呢?

 

三、最简模型设计与SQL实现

 

我们可以做一个最小化的例子来示例说明一下该如何实现,以下是最简化的设计说明,实际情况当然远比这要复杂的多!

 

首先,创建表:

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
CREATE TABLE TRAIN_SEATS (    SEAT_ID NUMBER PRIMARY KEY,    TRAIN_ID VARCHAR2(10),    CAR_NO NUMBER,    SEAT_NO VARCHAR2(3),    SEGMENT NUMBER,    STATUS VARCHAR2(10));

 

插入示例数据:

 

  •  
  •  
  •  
  •  
  •  
  •  
INSERT INTO TRAIN_SEATS VALUES (1, 'G322', 10, '13F', 1, 'AVAILABLE');INSERT INTO TRAIN_SEATS VALUES (2, 'G322', 10, '13F', 2, 'OCCUPIED');INSERT INTO TRAIN_SEATS VALUES (3, 'G322', 10, '13F', 3, 'AVAILABLE');INSERT INTO TRAIN_SEATS VALUES (4, 'G322', 10, '07C', 1, 'OCCUPIED');INSERT INTO TRAIN_SEATS VALUES (5, 'G322', 10, '07C', 2, 'AVAILABLE');INSERT INTO TRAIN_SEATS VALUES (6, 'G322', 10, '07C', 3, 'AVAILABLE');

 

以下是SQL实现的代码,具体如下:

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
WITH AVAILABLE_SEATS AS (    SELECT         CAR_NO,              -- 车厢号        SEAT_NO,             -- 座位号        SEGMENT,             -- 行程段(1:福州到杭州,2:杭州到南京,3:南京到北京)        STATUS,              -- 座位状态        ROW_NUMBER() OVER (            PARTITION BY SEGMENT             ORDER BY CAR_NO, SEAT_NO        ) AS RN               -- 为每个行程段的可用座位分配一个唯一的行号    FROM TRAIN_SEATS    WHERE TRAIN_ID = 'G322'   -- 只选择G322列车的座位    AND STATUS = 'AVAILABLE'  -- 只选择可用的座位)SELECT     CASE         -- 如果三个行程段都有可用座位,则可以全程乘坐        WHEN COUNT(DISTINCT SEGMENT) = 3 THEN '可以全程乘坐'        -- 如果至少有一个行程段有可用座位,则需要换座        WHEN COUNT(DISTINCT SEGMENT) >= 1 THEN '需要换座'        -- 如果没有任何可用座位,则无法完成行程        ELSE '无法完成行程'    END AS TRAVEL_PLAN,    -- 使用LISTAGG函数将所有可用座位信息连接成一个字符串    LISTAGG('段' || SEGMENT || ':' || CAR_NO || '车' || SEAT_NO || '座', ' -> ')     WITHIN GROUP (ORDER BY SEGMENT) AS SEAT_ARRANGEMENTFROM AVAILABLE_SEATSWHERE RN = 1  -- 只选择每个行程段的第一个可用座位GROUP BY RN;  -- 由于RN总是1,这里的GROUP BY实际上是将所有结果合并成一行

 

SQL的详细说明:

 

1、AVAILABLE_SEATS 子查询:

 

  • 从TRAIN_SEATS表中选择G322列车的所有可用座位。

     

  • 使用ROW_NUMBER()函数为每个行程段的可用座位分配一个唯一的行号。这允许我们后续只选择每个段的第一个可用座位。

 

2、主查询:

 

  • CASE语句用于确定旅行计划:

    如果所有3个段都有可用座位,则可以全程乘坐。

    如果至少有1个段有可用座位,则需要换座。

    如果没有任何可用座位,则无法完成行程。

 

  • LISTAGG函数用于生成一个字符串,描述每个段的可用座位。

     

  • WHERE RN = 1 确保我们只选择每个段的第一个可用座位。

     

  • GROUP BY RN 将结果合并成一行,因为RN总是1。

 

这个查询有效地找出了允许换座情况下的最佳座位组合,展示了如果系统支持分段座位分配,是可以完成全程订票的。

 

具体实现结果如下所示:

 

  •  
  •  
  •  
TRAVEL_PLAN | SEAT_ARRANGEMENT------------+--------------------------------------------------需要换座     | 段1:10车13F座 -> 段2:10车07C座 -> 段3:10车07C座

 

这个结果准确地反映了笔者的分析:

 

  • 福州到杭州(段1)坐10车13F

  • 杭州到南京(段2)换到10车07C

  • 南京到北京(段3)继续坐10车07C

 

这个查询展示了如果系统支持分段座位分配,是可以完成全程订票的。

 

看来打破现有系统(只能查询和分配全程相同的座位)的局限性,允许分段换座在理论上是比较容易实现的

 

四、车内换座功能成功上线

 

近日,12306果真采纳了笔者的建议,上线了“车内换座”功能,即乘坐同一车次时分段购票,中途乘客无需下车便可直接在车内更换座位。

 

 

当时笔者在车上写下上述的这篇文章,本意是希望铁路部门能有所动作,但也考虑到技术圈子相对小众,不一定能被相关部门看到。其实,更多的是想让技术人员明白“俯首皆学问”的道理,给大家一些启发,没想到事情还真的有了转机。

 

到了北京参加技术大会,第一天在大会负责人引荐下,笔者见到了恰好来参会分享的12306技术专家,并进行了简单的交流,他表示他和同事们都看到了文章,对此表示认可和感谢!笔者最后说,希望12306能早日实现这个功能。他则回应说:“一定一定”。没想到一个月后的今天,这个功能真的上线了。

 

五、回顾总结经验,分享交流思考

 

对于此事,笔者做了几点总结,也算是一个经验分享,希望能对广大技术从业人员有所帮助。

 

其一:乐于分享,收获惊喜

 

笔者在高铁上写的文章,带来了一些小惊喜和成就感。能够不经意间为社会一份力量,笔者感到很开心。笔者最大的成就,是看到读者在其影响下取得进步。许多人表示,看了其写的数据库书籍后,改变了他们的职业轨迹,让他们走上了数据库技术之路,这让笔者由衷地感到快乐。这也激励其不断提升自己的技术水平,促进自身的成长。因此,笔者想说,分享不仅能帮助他人,还能促进自己的进步和扩大影响力,是一件非常有意义的事情。

 

其二:融入圈子,拓展视野

 

分享多了,自然也就会融入到圈子中,而圈子对一个技术人员对成长至关重要。参加技术大会就是融入数库圈子最好的方式之一,比如在上个月的大会上,笔者与许多不同领域的技术专家进行了深入讨论,包括12306的专家,这是一个难得的跨行业接触和成长的机会。此外,笔者还活跃在dbaplus社群等多个技术社区中,大家可以积极参与社区举办的各类活动,共同交流进步。

 

在技术交流和实践中,工具和平台的支持也至关重要。特别感谢达梦提供的达梦在线服务平台,使笔者能在高铁上方便地验证代码。这种随时随地的便利,彰显了圈子的力量和朋友遍天下的好处。谈到具体的数据库产品方向,笔者建议大家拥抱原创的国产数据库,如:达梦、OceanBase、TiDB、Kingwow(金乌)、虚谷、Databend等,与时代共前行!

 

其三:停止抱怨,深入思考

 

这不仅仅是技术从业人员的问题。很多人遇到问题时,没有深入思考,也没有提出合适的解决方案,而是不断抱怨,这并没有什么作用。就拿笔者乘车改签失败这件事来说,如果仅仅停留在抱怨和投诉层面,是很难促成12306去完成这项改进的。如果你有深入思考,并给出详细的解决方案,那情况就会大有不同。

 

希望大家在心态上能积极主动,停止抱怨,深入思考,精准建议。

 

此外,笔者将于11月29日出席dbaplus社群在上海举办的第九届DAMS中国数据智能管理峰会,届时将分享《数智化时代打造超融合数据库》的主题演讲,欢迎大家前来一起交流,了解大会详情及报名参会可扫描下图二维码:
图片
最新评论
访客 2024年04月08日

如果字段的最大可能长度超过255字节,那么长度值可能…

访客 2024年03月04日

只能说作者太用心了,优秀

访客 2024年02月23日

感谢详解

访客 2024年02月20日

一般干个7-8年(即30岁左右),能做到年入40w-50w;有…

访客 2023年08月20日

230721

活动预告