SQL编程大师-柳胜勋:算法与逻辑解析,如何在 1.201 秒内用 Doris 完成百万车票复杂分配
数据库编程大赛:只用一条 SQL 秒杀 100 万张火车票
2024 第二届数据库编程大赛于 12 月 5 日正式开启初赛!由 NineData 和云数据库技术社区主办,华为云、Doris等协办单位和媒体共同举办。比赛要求选手设计一套SQL算法,只用一条 SQL 秒杀 100 万张火车票,让乘客都都能顺利坐上火车回家过年。查看赛题详情
以下是本次决赛第2名,SQL编程大师柳胜勋的参赛介绍:
1. 因为有10%的站票,而且必须买完所有坐票才能买站票,所以我们需要把座位票和无座票分开来卖。即把一列车变成成两列车卖,一列是有座的票,二列是10%的无座票。
2. 先处理有座的车,根据路径分组,车号排序。计算出每列车的起始编号。按照 200个座位一页拆分。比如800座的火车,拆分成4个页,也就是变成4条记录,以此类推。
3. 拆分后,使用窗口函数进行编号座位页号,根据路径分组,车号排序。因为每页的大小都是一样的。所以 路径+页号 是唯一的。
4. 同时,我们使用函数窗口,计算出同一条线路上的乘客编号。
5. 因为有页号,所以可以乘客编号可以对 200 取模,使用JOIN快速映射到记录。
6. 通过这次JOIN后,有座的票就分好了。同时可以根据编号减去列车的起始编号,获得列车上的座位序号,然后除法和取余计算出座位号。
7. 将上一步的结果筛选出未分配列车的乘客,和上面一样的方式,只是按照20一页来取模计算页号。因为无座,所以不需要算车厢号,座位号直接标记无座即可。
1.首先生成列车的车票信息。借用自增数列t_sequence,通过与train连接,生成车厢号、座位号。另外,生成10%的无座车票。为便于对车票排序,额外生成是否有座说明列。
2.以出发站和到达站点对高铁车次分组,并按照是否有座对可售车票进行顺序编号,有座车票编号在前,无座车票编号在后。
3.乘客按照出发站和到达站点进行顺序编号。
4.车票和乘客顺序编号之后,根据出发站、到达站点、顺序编号对可售座票与乘客进行外连接,即完成车票分配。
性能优化考虑:
由于是全量数据连接操作,表字段个数少,且表连接操作都是在中间结果集之后进行的,创建索引没有多大意义,故无需额外创建索引。 若机器性能较好,可考虑添加并行hint,使用并行查询技术。 可在排序内存和hash连接内存分配上多给一些,消除在磁盘上的排序。
With T AS (WITH extension AS (SELECT600 AS sizeUNION ALLSELECT600UNION ALLSELECT600UNION ALLSELECT800UNION ALLSELECT800UNION ALLSELECT800UNION ALLSELECT800UNION ALLSELECT1200UNION ALLSELECT1200UNION ALLSELECT1200UNION ALLSELECT1200UNION ALLSELECT1200UNION ALLSELECT1200UNION ALLSELECT1600UNION ALLSELECT1600UNION ALLSELECT1600UNION ALLSELECT1600UNION ALLSELECT1600UNION ALLSELECT1600UNION ALLSELECT1600UNION ALLSELECT1600),t1 AS(SELECTtrain_id,departure_station,arrival_station,seat_count,SUM(seat_count) OVER(PARTITION BY departure_station,arrival_stationORDER BYtrain_id) AS seat_cumsumFROMtrain)SELECTdeparture_station,arrival_station,train_id,seat_cumsum - seat_count + 1 AS seat_start,ROW_NUMBER() OVER (PARTITION BY departure_station,arrival_stationORDER BYt1.train_id) AS page_id,"无座" AS seat_numberFROMt1LEFT JOIN extension E ON E.size = t1.seat_count),S AS (WITH seatNumbers AS (SELECTROW_NUMBER() OVER () AS seat_numberfrompassengerlimit100)SELECTseat_number,CONCAT(FLOOR((seat_number -1) / 5) + 1,SUBSTRING('ABCEF', (seat_number - 1) % 5 + 1, 1)) AS seat_labelFROMseatNumbers),P AS (SELECTROW_NUMBER() OVER (PARTITION BY departure_station,arrival_stationORDER BYpassenger_id) AS seat_id,passenger_id,departure_station,arrival_stationFROMpassenger),A1T AS(SELECTP.passenger_id,P.departure_station,P.arrival_station,T.train_id AS train_id,P.seat_id - T.seat_start AS train_seat_idFROMPLEFT JOIN T ON P.departure_station = T.departure_stationAND P.arrival_station = T.arrival_stationAND T.page_id = FLOOR((P.seat_id - 1) / 200) + 1),P2R AS(SELECTpassenger_id,departure_station,arrival_stationFROMA1TWHEREtrain_id IS NULL),P2M AS(SELECTROW_NUMBER() OVER (PARTITION BY departure_station,arrival_stationORDER BYpassenger_id) AS seat_id,passenger_id,departure_station,arrival_stationFROMP2R),AF AS(SELECTpassenger_id,departure_station,arrival_station,train_id,FLOOR(train_seat_id / 100) + 1 AS coach_number,S.seat_label AS seat_numberFROMA1TLEFT JOIN S ON seat_number = train_seat_id % 100 + 1WHEREtrain_id IS NOT NULLORDER BYpassenger_idUNION ALLSELECTP2M.passenger_id,P2M.departure_station,P2M.arrival_station,T.train_id AS train_id,NULL AS coach_number,seat_numberFROMP2MLEFT JOIN T ON P2M.departure_station = T.departure_stationAND P2M.arrival_station = T.arrival_stationAND T.page_id = FLOOR((P2M.seat_id - 1) / 20) + 1)SELECTpassenger_id,departure_station,arrival_station,train_id,coach_number,seat_numberFROMAFORDER BYpassenger_id;
感谢大家对本次《数据库编程大赛》的关注和支持,欢迎加入技术交流群,更多精彩活动不断,欢迎各路数据库爱好者来挑战!