admin
-
标题:达梦冯裕才教授获CCF 2024最高科学技术奖;OceanBase 社区年终嘉年华将于周六在北京举行
重要更新
达梦数据库创始人冯裕才教授获2024年“CCF最高科学技术奖”[1]。获奖理由:“冯裕才教授是我国著名数据库科技工作者,长期致力于我国自主数据库的研发和产业化工作,突破了数据库共享存储集群技术,开发了达梦系列数据库,打造了国内首家数据库上市企业“达梦数据”,为推动我国基础软件产业的发展做出了重要贡献。”
OceanBase 社区年终嘉年华将于周六在北京举行[2]。OceanBase 将携手 360、TuGraph,邀请 360、作业帮、Boss 直聘、好未来等企业的技术大咖,共话 OceanBase 在各行业的应用实践和技术选型心得。感谢可以现场参加。
更新详情
阿里云
火山云(字节)
- 云数据库 MySQL支持手动对实例的内核小版本进行升级 [5]
- 云数据库 MySQL支持为实例创建跨地域的灾备实例(邀测),为实例提供跨地域的灾备能力[6]
- 支持在特定时间段内发起实例变配、重启、迁移、升级版本等操作,以增强实例管理的灵活性[7]
GCP(谷歌云)
- BigQuery 发布新版本的 JDBC 驱动程序][8]
参考链接
- [1] https://mp.weixin.qq.com/s/nR4iIK-6m6elVh4p-kLwgQ
- [2] https://mp.weixin.qq.com/s/60885pIuoObj1fxjLNBU3A
- [3] https://help.aliyun.com/zh/rds/apsaradb-rds-for-postgresql/new-features-new-specifications-apsaradb-rds-for-postgresql-standard-instances-that-run-rds-high-availability-edition-support-larger-dedicated-specifications
- [4] https://help.aliyun.com/zh/rds/apsaradb-rds-for-sql-server/create-a-system-admin-account-for-an-apsaradb-rds-for-sql-server-instance
- [5] https://www.volcengine.com/docs/6313/1414461
- [6] https://www.volcengine.com/docs/6313/1411527
- [7] https://www.volcengine.com/docs/6313/79845
- [8] https://cloud.google.com/bigquery/docs/reference/odbc-jdbc-drivers
-
这是第二次 SQL 编程大赛[1],我依旧是评委之一,所以自己也尝试了独立完成该问题的挑战。这次大赛分为“普通挑战”和“进阶挑战”。其中普通挑战较为简单,本文主要讨论自己完成进阶挑战过程中的想法与思路。
目录
问题描述
原始的问题,可以参考:NineData 第二届数据库编程大赛 用一条SQL秒杀火车票,本文仅考虑其中的“进阶挑战”。这里该“进阶挑战问题”复述如下。
有如下两张表存放着乘客信息和列车信息,使用一条SQL给每个乘客分配一趟列车以及对应的座位号,需要注意,需要考虑进阶挑战的一些要求,比如,每趟列车可以发售10%的无座车票;车票需要有限发售有座车票,然后才开始发售无座车票。
mysql> desc passenger; +-------------------+-------------+------+-----+ | Field | Type | Null | Key | +-------------------+-------------+------+-----+ | passenger_id | varchar(16) | NO | PRI | | departure_station | varchar(32) | NO | | | arrival_station | varchar(32) | NO | | +-------------------+-------------+------+-----+
mysql> desc train; +-------------------+-------------+------+-----+ | Field | Type | Null | Key | +-------------------+-------------+------+-----+ | train_id | varchar(8) | NO | PRI | | departure_station | varchar(32) | NO | | | arrival_station | varchar(32) | NO | | | seat_count | int | NO | | +-------------------+-------------+------+-----+
示例数据如下:
mysql> select * from passenger limit 3; +--------------+-------------------+-----------------+ | passenger_id | departure_station | arrival_station | +--------------+-------------------+-----------------+ | P00000001 | 上海 | 福州 | | P00000002 | 成都 | 成都 | | P00000003 | 乌鲁木齐 | 太原 | +--------------+-------------------+-----------------+
mysql> select * from train limit 3; +----------+-------------------+-----------------+------------+ | train_id | departure_station | arrival_station | seat_count | +----------+-------------------+-----------------+------------+ | G1006 | 重庆 | 北京 | 1600 | | G1007 | 杭州 | 福州 | 600 | | G1008 | 济南 | 合肥 | 800 | +----------+-------------------+-----------------+------------+
解题思路
对乘客进行编号
首先利用数据库的
Windows Function
功能对所有的乘客先分组再编号,具体的,按照“出发站”和“到达站”分组,然后在组内进行编号。次编号则为后续乘客车票分配的编号。例如,从 A 到 B 地,一共有 2420 个乘客。那么乘客的编号则是1…2420;再有乘客从 C 到 D 地,共有1800个乘客,则编号则为 1 … 1800。大概可以使用类似如下的 SQL 代码实现:ROW_NUMBER() over(PARTITION BY departure_station,arrival_station) as seq
对列车进行排序和计算
与乘客类似的,先按照出发和到达站点进行分组,并计算每个列车能够容纳的乘客数量,即座位数量的 1.1 倍。然后,在分组内进行“累加”计算,该累加计算,需算出每个列车能够运载乘客的起始序号和结束序号。例如,从 A 到 B地,共有列车 G01 和 G07 ,并分别有 600 和 1600 个座位。那么,经过上述的累加计算,列车 G01 能够运载的乘客编号应该是 1 到 660,而 G01 能够运载的乘客编号则为 661 到 2420 (即为 660 + 1600*110%)。
上述计算也可以使用 Window Function来实现,参考实现如下:
sum(seat_count*1.1) over ( PARTITION BY departure_station,arrival_station ORDER BY train_id ) as p_seat_to ,
合并计算结果
然后,将上述经过计算的乘客表和列车表进行 JOIN ,条件是 起始站和到达站相同,且乘客编号在列车编号之间。如果,乘客无法关联出列车,则表示无法分配列车。
该方案的最终 SQL
SELECT p_01.p_id, p_01.d_s, p_01.a_s, t_01.train_id as t_id, p_01.seq, -- passager seq from d_s to a_s t_01.seat_count, @p_seat_from := (t_01.p_seat_to-t_01.seat_count*1.1 + 1) as seat_from, -- train seat from(start index) t_01.p_seat_to as seat_to, -- train seat from(start index) if(p_01.seq >= p_seat_to-seat_count*0.1 + 1, "ti_no_seat","...") as ti_no_seat, @seq_in_train := p_01.seq - @p_seat_from + 1 as seq_in_train, -- seq in the train @carriage_id := ceil(@seq_in_train/100) as t_carr_id, -- for carriage id @row_id := ceil((@seq_in_train%100)/5) as row_id, -- row_id @seat_id := ceil((@seq_in_train%100)%5) seat_id -- 0,1,2,3,4 A B C E F FROM ( select ROW_NUMBER() over(PARTITION BY departure_station,arrival_station) as seq , passenger_id as p_id, departure_station as d_s, arrival_station as a_s from passenger ) as p_01 LEFT JOIN ( select seat_count, sum(seat_count*1.1) over ( PARTITION BY departure_station,arrival_station ORDER BY train_id ) as p_seat_to , train_id, departure_station as d_s , arrival_station as a_s from train ) t_01 ON p_01.seq >= p_seat_to-seat_count*1.1 + 1 and p_01.seq <= p_seat_to and p_01.d_s = t_01.d_s and p_01.a_s = t_01.a_s
上述实现的问题
这样的实现,是可以完成相关的座位分配。但是,却会出现一个不合理的情况,即可能有车次的座位没有分配完,但是有一部分乘客却被分配到了无座的车次。比如,从A到B的车次,有两班,第一班车600个座位,第二版1600个座位,一共有 800 个乘客的话,那么这里分配自由度就比较高,比如这种情况依旧分配了 220 个无座的座位,是否是满足要求的。
在最初,该赛题还未对外发布时,是没有该限制的。而后,发现该漏洞后,新增了一个规则,即需要先把有座的票优先分配,再分配无座的车票。
考虑优先分配有座
考虑优先分配有座的车票,再对上述实现进行一定程度的修改。
重新考虑对列车的编号和计算
对于每一趟列车
X
,构造一个虚拟列车X'
,该虚拟列车X'
虚拟的负责所有的X
列车的座票。而在给列车中计算起始和结束乘客编号时,则优先计算原列车的编号范围,在所有的原列车编号计算完成后,再计算X'
的乘客编号范围。这里使用 CTEs 实现该表达如下:
WITH t_no_seat_virtual AS ( select train_id as t_id,departure_station as d_s,arrival_station as a_s,seat_count, seat_count*0.1 as seat_count_no_seat from train ), t_include_no_seat AS ( select t_id,d_s ,a_s ,seat_count, "with_seat" as if_seat from t_no_seat_virtual union select t_id,d_s ,a_s ,seat_count_no_seat, "no_seat" as if_seat from t_no_seat_virtual) SELECT * from t_include_no_seat ORDER BY t_id
包含虚拟列车的表 t_include_no_seat
然后把上面的实现中,表train替换成这里的 t_include_no_seat ,包含了额外的“虚拟列车”,完整的 SQL 如下:
WITH t_no_seat_virtual AS ( select train_id as t_id,departure_station as d_s,arrival_station as a_s,seat_count, seat_count*0.1 as seat_count_no_seat from train ), t_include_no_seat AS ( select t_id,d_s ,a_s ,seat_count, 0 as if_no_seat from t_no_seat_virtual union select t_id,d_s ,a_s ,seat_count_no_seat, 1 as if_no_seat from t_no_seat_virtual) select seat_count, sum(seat_count) over ( PARTITION BY d_s,a_s ORDER BY t_id,if_no_seat ) as p_seat_to , t_id, d_s , a_s, if_no_seat from t_include_no_seat
返回的结果如下:
+------------+-----------+------+--------+--------+------------+ | seat_count | p_seat_to | t_id | d_s | a_s | if_no_seat | +------------+-----------+------+--------+--------+------------+ | 600.0 | 600.0 | G109 | 上海 | 北京 | 0 | | 60.0 | 660.0 | G109 | 上海 | 北京 | 1 | | 1600.0 | 2260.0 | G40 | 上海 | 北京 | 0 | | 160.0 | 2420.0 | G40 | 上海 | 北京 | 1 | | 1600.0 | 4020.0 | G70 | 上海 | 北京 | 0 | | 160.0 | 4180.0 | G70 | 上海 | 北京 | 1 | | 1600.0 | 1600.0 | G113 | 上海 | 广州 | 0 | | 160.0 | 1760.0 | G113 | 上海 | 广州 | 1 | | 1600.0 | 3360.0 | G26 | 上海 | 广州 | 0 | | 160.0 | 3520.0 | G26 | 上海 | 广州 | 1 | | 1600.0 | 5120.0 | G48 | 上海 | 广州 | 0 | | 160.0 | 5280.0 | G48 | 上海 | 广州 | 1 | | 1600.0 | 1600.0 | G52 | 上海 | 成都 | 0 | | 160.0 | 1760.0 | G52 | 上海 | 成都 | 1 | | 1600.0 | 3360.0 | G8 | 上海 | 成都 | 0 | | 160.0 | 3520.0 | G8 | 上海 | 成都 | 1 | | 1600.0 | 1600.0 | G107 | 上海 | 武汉 | 0 | | 160.0 | 1760.0 | G107 | 上海 | 武汉 | 1 | | 1600.0 | 3360.0 | G17 | 上海 | 武汉 | 0 | | 160.0 | 3520.0 | G17 | 上海 | 武汉 | 1 |
这里需要注意的是,编号的 ORDER BY 需要按照 if_no_seat,t_id 进行排序,这样就可以保障,优先分配有座位的位置。
WITH t_no_seat_virtual AS ( select train_id as t_id,departure_station as d_s,arrival_station as a_s,seat_count, seat_count*0.1 as seat_count_no_seat from train ), t_include_no_seat AS ( select t_id,d_s ,a_s ,seat_count, 0 as if_no_seat from t_no_seat_virtual union select t_id,d_s ,a_s ,seat_count_no_seat, 1 as if_no_seat from t_no_seat_virtual) select sum(seat_count) over ( PARTITION BY d_s,a_s ORDER BY if_no_seat,t_id ) as p_seat_to , seat_count, t_id, d_s , a_s, if_no_seat from t_include_no_seat
按照乘客序号分配座位
与前述的实现相同,首先按照始发和到达站点将旅客表与“虚拟列车”表关联。如果自己序列落在某个列车的区间中就表示有座位。
关于 row 的分配异常问题
按照上述的分配,会将编号为 100 背书的人,分配为 0F,而正确的应该是 20 F。所以,需要额外处理该数值。具体的:
ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)/5)
修改如下:
IF( (p_01.seq-t_01.p_seat_to + t_01.seat_count)%100 = 0, "20" ,ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)/5))
这部分代码实现较为冗长,更好的方法是先计算偏倚值,然后使用字符串截取函数截取,而无需写这么多的
CASE ... WHEN
。完整的SQL
WITH t_no_seat_virtual AS ( select train_id as t_id,departure_station as d_s,arrival_station as a_s,seat_count, seat_count*0.1 as seat_count_no_seat from train ), t_include_no_seat AS ( select t_id,d_s ,a_s ,seat_count, 0 as if_no_seat from t_no_seat_virtual union select t_id,d_s ,a_s ,seat_count_no_seat, 1 as if_no_seat from t_no_seat_virtual) SELECT p_01.p_id, p_01.d_s, p_01.a_s, t_01.t_id as t_id, p_01.seq, -- passager seq from d_s to a_s t_01.seat_count, t_01.if_no_seat, @p_seat_from := (t_01.p_seat_to-t_01.seat_count + 1) as seat_from, -- train seat from(start index) t_01.p_seat_to as seat_to, -- train seat from(start index) @seq_in_train := p_01.seq - @p_seat_from + 1 as seq_in_train, -- seq in the train @carriage_id := ceil(@seq_in_train/100) as t_carr_id, -- for carriage id @row_id := ceil((@seq_in_train%100)/5) as row_id, -- row_id @seat_id := ceil((@seq_in_train%100)%5) seat_id, -- 0,1,2,3,4 A B C E F CASE WHEN @seat_id = 1 THEN CONCAT(@row_id,"A") WHEN @seat_id = 2 THEN CONCAT(@row_id,"B") WHEN @seat_id = 3 THEN CONCAT(@row_id,"C") WHEN @seat_id = 4 THEN CONCAT(@row_id,"E") WHEN @seat_id = 0 THEN CONCAT(@row_id,"F") ELSE "ERROR" END as seat_index FROM ( select ROW_NUMBER() over(PARTITION BY departure_station,arrival_station) as seq , passenger_id as p_id, departure_station as d_s, arrival_station as a_s from passenger ) as p_01 LEFT JOIN ( select seat_count, sum(seat_count) over ( PARTITION BY d_s,a_s ORDER BY if_no_seat,t_id ) as p_seat_to , t_id, d_s , a_s , if_no_seat from t_include_no_seat ) t_01 ON p_01.seq >= p_seat_to-seat_count + 1 and p_01.seq <= p_seat_to and p_01.d_s = t_01.d_s and p_01.a_s = t_01.a_s
修正SQL
最后按照题目要求,对输出结果做一些修正。具体的:
- 按要求,如果分配座位为无座的,则在车厢号展示为””,座位号显示”无座”。
- 删除中间计算结果,为了保证性能,就不在外面再套一层了,事实上,套一层可读性会更好
WITH t_no_seat_virtual AS ( select train_id as t_id, departure_station as d_s, arrival_station as a_s, seat_count, seat_count*0.1 as seat_count_no_seat from train ), t_include_no_seat AS ( select t_id,d_s ,a_s ,seat_count, 0 as if_no_seat from t_no_seat_virtual union select t_id,d_s ,a_s ,seat_count_no_seat, 1 as if_no_seat from t_no_seat_virtual) SELECT p_01.p_id, p_01.d_s, p_01.a_s, t_01.t_id as t_id, p_01.seq, -- passager seq from d_s to a_s t_01.seat_count, t_01.if_no_seat, @p_seat_from := (t_01.p_seat_to-t_01.seat_count + 1) as seat_from, -- train seat from(start index) t_01.p_seat_to as seat_to, -- train seat from(start index) @seq_in_train := p_01.seq - @p_seat_from + 1 as seq_in_train, -- seq in the train -- @carriage_id := ceil(@seq_in_train/100) as t_carr_id, -- for carriage id IF(if_no_seat, "" , @carriage_id := ceil(@seq_in_train/100) ) as t_carr_id, @row_id := ceil((@seq_in_train%100)/5) as row_id, -- row_id @seat_id := IF( !isnull(t_01.t_id) and if_no_seat,-1,ceil((@seq_in_train%100)%5)) as seat_id, -- 0,1,2,3,4 A B C E F CASE WHEN @seat_id = 1 THEN CONCAT(@row_id,"A") WHEN @seat_id = 2 THEN CONCAT(@row_id,"B") WHEN @seat_id = 3 THEN CONCAT(@row_id,"C") WHEN @seat_id = 4 THEN CONCAT(@row_id,"E") WHEN @seat_id = 0 THEN CONCAT(@row_id,"F") WHEN @seat_id = -1 THEN "无座" ELSE NULL END as seat_index FROM ( select ROW_NUMBER() over(PARTITION BY departure_station,arrival_station) as seq , passenger_id as p_id, departure_station as d_s, arrival_station as a_s from passenger ) as p_01 LEFT JOIN ( select seat_count, sum(seat_count) over ( PARTITION BY d_s,a_s ORDER BY if_no_seat,t_id ) as p_seat_to , t_id, d_s , a_s , if_no_seat from t_include_no_seat ) t_01 ON p_01.seq >= p_seat_to-seat_count + 1 and p_01.seq <= p_seat_to and p_01.d_s = t_01.d_s and p_01.a_s = t_01.a_s
正确性验证
这个 SQL 的正确性并不好验证。而事实上,只要能够正确的完成这条SQL,基本上就已经打败了80%的选手了。如果性能再有一些优化,基本上已经是前10%的选手。
这里从以下几个方面对SQL正确性做初步验证:
- 对于每一个系列(始发站和到达站相同)的车次进行统计,统计座位数量和旅客数量,然后看实际分配情况是否符合
- 手动检车车厢、座位号分配的情况
- 检查某个系列,无座和有座的乘客数量
座位供需统计
座位的供需情况有如下三种:(a) 座票供应充值 (b) 加上无座后供应充值 (c) 供应不足。完整的供需计算如下:
case #1 ti_supply |__________________________|___________| ti_needed |<------------------->| case #2 ti_supply |__________________________|___________| ti_needed |<------------------------------>| case #3 ti_supply |__________________________|___________| ti_needed |<-------------------------------------------->| -- ti_ always short for tickets -- p_ always short for passager -- t_ always short for train select p_needed.d_s, p_needed.a_s, ti_needed, ti_supply, if( ti_needed > ti_supply, ti_needed - ti_supply , 0 ) as p_without_ti, if( ti_needed > ti_supply/1.1 , round(ti_supply/1.1,0) , ti_needed ) as p_with_ti_with_seat, if( ti_needed <= ti_supply/1.1 , -- case #1 0, if( ti_needed <= ti_supply, -- case #2 round(ti_needed - ti_supply/1.1,0) , ti_supply/11 -- case #3 ) ) as p_with_ti_without_seat from ( select -- passenger_id as p_id, departure_station as d_s, arrival_station as a_s, count(1) as ti_needed from passenger group by departure_station,arrival_station ) p_needed , ( select -- train_id t_id, departure_station as d_s , arrival_station as a_s, 1.1*sum(seat_count) as ti_supply from train group by departure_station,arrival_station ) t_supply WHERE p_needed.d_s = t_supply.d_s and p_needed.a_s = t_supply.a_s
返回的供需表如下:
+--------+--------+-----------+-----------+--------------+---------------------+------------------------+ | d_s | a_s | ti_needed | ti_supply | p_without_ti | p_with_ti_with_seat | p_with_ti_without_seat | +--------+--------+-----------+-----------+--------------+---------------------+------------------------+ | 上海 | 北京 | 2460 | 4180.0 | 0 | 2460 | 0 | | 上海 | 广州 | 2406 | 5280.0 | 0 | 2406 | 0 | | 上海 | 成都 | 2421 | 3520.0 | 0 | 2421 | 0 | | 上海 | 武汉 | 2454 | 7700.0 | 0 | 2454 | 0 | | 上海 | 深圳 | 2388 | 7040.0 | 0 | 2388 | 0 | | 北京 | 上海 | 2381 | 1760.0 | 621.0 | 1600 | 160.00000 | | 北京 | 广州 | 2448 | 3520.0 | 0 | 2448 | 0 | | 北京 | 成都 | 2384 | 3520.0 | 0 | 2384 | 0 | | 北京 | 杭州 | 2478 | 3520.0 | 0 | 2478 | 0 | | 北京 | 武汉 | 2404 | 5940.0 | 0 | 2404 | 0 | | 北京 | 深圳 | 2342 | 3520.0 | 0 | 2342 | 0 | | 广州 | 上海 | 2339 | 4180.0 | 0 | 2339 | 0 | | 广州 | 北京 | 2368 | 1760.0 | 608.0 | 1600 | 160.00000 | | 广州 | 成都 | 2332 | 3520.0 | 0 | 2332 | 0 | | 广州 | 杭州 | 2407 | 5280.0 | 0 | 2407 | 0 | | 广州 | 武汉 | 2320 | 3520.0 | 0 | 2320 | 0 | | 广州 | 深圳 | 2352 | 1760.0 | 592.0 | 1600 | 160.00000 | | 成都 | 上海 | 2422 | 4180.0 | 0 | 2422 | 0 | | 成都 | 北京 | 2318 | 5940.0 | 0 | 2318 | 0 | | 成都 | 广州 | 2450 | 1760.0 | 690.0 | 1600 | 160.00000 | | 成都 | 杭州 | 2343 | 5280.0 | 0 | 2343 | 0 | | 成都 | 武汉 | 2415 | 5280.0 | 0 | 2415 | 0 | | 成都 | 深圳 | 2364 | 2420.0 | 0 | 2200 | 164 | | 杭州 | 北京 | 2389 | 1760.0 | 629.0 | 1600 | 160.00000 | | 杭州 | 成都 | 2370 | 1760.0 | 610.0 | 1600 | 160.00000 | | 杭州 | 深圳 | 2387 | 10560.0 | 0 | 2387 | 0 | | 武汉 | 上海 | 2323 | 5280.0 | 0 | 2323 | 0 | | 武汉 | 北京 | 2453 | 5280.0 | 0 | 2453 | 0 | | 武汉 | 广州 | 2395 | 10560.0 | 0 | 2395 | 0 | | 武汉 | 成都 | 2337 | 1760.0 | 577.0 | 1600 | 160.00000 | | 武汉 | 杭州 | 2428 | 3520.0 | 0 | 2428 | 0 | | 武汉 | 深圳 | 2390 | 5280.0 | 0 | 2390 | 0 | | 深圳 | 上海 | 2251 | 3520.0 | 0 | 2251 | 0 | | 深圳 | 北京 | 2309 | 7040.0 | 0 | 2309 | 0 | | 深圳 | 成都 | 2341 | 3520.0 | 0 | 2341 | 0 | | 深圳 | 杭州 | 2412 | 660.0 | 1752.0 | 600 | 60.00000 | | 深圳 | 武汉 | 2329 | 10120.0 | 0 | 2329 | 0 | +--------+--------+-----------+-----------+--------------+---------------------+------------------------+
SQL 计算返回结果统计
先使用
CREATE TABLE t_ret ...
将结果集存储一个中间的临时表。然后,再计算 SQL返回结果表中的统计数据:
SELECT d_s,a_s, CASE WHEN ISNULL(seat_index) THEN "p_without_ti" WHEN seat_index = "无座" THEN "p_with_ti_without_seat" ELSE "p_with_ti_with_seat" END as p_status, COUNT(1) FROM t_ret GROUP BY d_s,a_s , p_status
返回:
+--------+--------+------------------------+----------+ | d_s | a_s | p_status | COUNT(1) | +--------+--------+------------------------+----------+ | 上海 | 北京 | p_with_ti_with_seat | 2460 | | 上海 | 广州 | p_with_ti_with_seat | 2406 | | 上海 | 成都 | p_with_ti_with_seat | 2421 | | 上海 | 杭州 | p_without_ti | 2373 | | 上海 | 武汉 | p_with_ti_with_seat | 2454 | | 上海 | 深圳 | p_with_ti_with_seat | 2388 | | 北京 | 上海 | p_with_ti_with_seat | 1600 | | 北京 | 上海 | p_with_ti_without_seat | 160 | | 北京 | 上海 | p_without_ti | 621 | | 北京 | 广州 | p_with_ti_with_seat | 2448 | | 北京 | 成都 | p_with_ti_with_seat | 2384 | | 北京 | 杭州 | p_with_ti_with_seat | 2478 | | 北京 | 武汉 | p_with_ti_with_seat | 2404 | | 北京 | 深圳 | p_with_ti_with_seat | 2342 | | 广州 | 上海 | p_with_ti_with_seat | 2339 | | 广州 | 北京 | p_with_ti_with_seat | 1600 | | 广州 | 北京 | p_with_ti_without_seat | 160 | | 广州 | 北京 | p_without_ti | 608 | | 广州 | 成都 | p_with_ti_with_seat | 2332 | | 广州 | 杭州 | p_with_ti_with_seat | 2407 | | 广州 | 武汉 | p_with_ti_with_seat | 2320 | | 广州 | 深圳 | p_with_ti_with_seat | 1600 | | 广州 | 深圳 | p_with_ti_without_seat | 160 | | 广州 | 深圳 | p_without_ti | 592 | | 成都 | 上海 | p_with_ti_with_seat | 2422 | | 成都 | 北京 | p_with_ti_with_seat | 2318 | | 成都 | 广州 | p_with_ti_with_seat | 1600 | | 成都 | 广州 | p_with_ti_without_seat | 160 | | 成都 | 广州 | p_without_ti | 690 | | 成都 | 杭州 | p_with_ti_with_seat | 2343 | | 成都 | 武汉 | p_with_ti_with_seat | 2415 | | 成都 | 深圳 | p_with_ti_with_seat | 2200 | | 成都 | 深圳 | p_with_ti_without_seat | 164 | | 杭州 | 上海 | p_without_ti | 2376 | | 杭州 | 北京 | p_with_ti_with_seat | 1600 | | 杭州 | 北京 | p_with_ti_without_seat | 160 | | 杭州 | 北京 | p_without_ti | 629 | | 杭州 | 广州 | p_without_ti | 2401 | | 杭州 | 成都 | p_with_ti_with_seat | 1600 | | 杭州 | 成都 | p_with_ti_without_seat | 160 | | 杭州 | 成都 | p_without_ti | 610 | | 杭州 | 武汉 | p_without_ti | 2353 | | 杭州 | 深圳 | p_with_ti_with_seat | 2387 | | 武汉 | 上海 | p_with_ti_with_seat | 2323 | | 武汉 | 北京 | p_with_ti_with_seat | 2453 | | 武汉 | 广州 | p_with_ti_with_seat | 2395 | | 武汉 | 成都 | p_with_ti_with_seat | 1600 | | 武汉 | 成都 | p_with_ti_without_seat | 160 | | 武汉 | 成都 | p_without_ti | 577 | | 武汉 | 杭州 | p_with_ti_with_seat | 2428 | | 武汉 | 深圳 | p_with_ti_with_seat | 2390 | | 深圳 | 上海 | p_with_ti_with_seat | 2251 | | 深圳 | 北京 | p_with_ti_with_seat | 2309 | | 深圳 | 广州 | p_without_ti | 2387 | | 深圳 | 成都 | p_with_ti_with_seat | 2341 | | 深圳 | 杭州 | p_with_ti_with_seat | 600 | | 深圳 | 杭州 | p_with_ti_without_seat | 60 | | 深圳 | 杭州 | p_without_ti | 1752 | | 深圳 | 武汉 | p_with_ti_with_seat | 2329 | +--------+--------+------------------------+----------+
从上述的两个结果对比来看,挑选了几个来看,数据是一致的。比如,“深圳->杭州”,上面表格计算得
| 深圳 | 杭州 | 2412 | 660.0 | 1752.0 | 600 | 60.00000 |
对比:
| 深圳 | 杭州 | p_with_ti_with_seat | 600 | | 深圳 | 杭州 | p_with_ti_without_seat | 60 | | 深圳 | 杭州 | p_without_ti | 1752 |
这里的 600、60、1752也是一致的。
最后按照输出进行调整
WITH t_no_seat_virtual AS ( select train_id as t_id, departure_station as d_s, arrival_station as a_s, seat_count, seat_count*0.1 as seat_count_no_seat from train ), t_include_no_seat AS ( select t_id,d_s ,a_s ,seat_count, 0 as if_no_seat from t_no_seat_virtual union select t_id,d_s ,a_s ,seat_count_no_seat, 1 as if_no_seat from t_no_seat_virtual ) SELECT p_01.p_id, -- output 01 p_01.d_s, -- output 02 p_01.a_s, -- output 03 t_01.t_id as t_id, -- output 04 IF( if_no_seat, "" , ceil((p_01.seq-t_01.p_seat_to + t_01.seat_count)/100) ) as t_carr_id, -- output 05 CASE IF( !isnull(t_01.t_id) and if_no_seat,-1,ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)%5)) WHEN 1 THEN CONCAT( ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)/5) ,"A") WHEN 2 THEN CONCAT( ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)/5) ,"B") WHEN 3 THEN CONCAT( ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)/5) ,"C") WHEN 4 THEN CONCAT( ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)/5) ,"E") WHEN 0 THEN CONCAT( IF( (p_01.seq-t_01.p_seat_to + t_01.seat_count)%100 = 0, "20" ,ceil((( p_01.seq-t_01.p_seat_to + t_01.seat_count )%100)/5)) ,"F") WHEN -1 THEN "无座" ELSE NULL END as seat_index -- output 06 FROM ( select ROW_NUMBER() over(PARTITION BY departure_station,arrival_station) as seq , passenger_id as p_id, departure_station as d_s, arrival_station as a_s from passenger ) as p_01 LEFT JOIN ( select seat_count, sum(seat_count) over ( PARTITION BY d_s,a_s ORDER BY if_no_seat,t_id ) as p_seat_to , t_id, d_s , a_s , if_no_seat from t_include_no_seat ) t_01 ON p_01.seq >= p_seat_to-seat_count + 1 and p_01.seq <= p_seat_to and p_01.d_s = t_01.d_s and p_01.a_s = t_01.a_s ORDER BY p_01.p_id
赛题与数据
原始的比赛题目参考:https://www.ninedata.cloud/sql_train2024 这里仅讨论其中的“进阶挑战”。
表定义(MySQL)
CREATE DATABASE `game_ticket`; use game_ticket; CREATE TABLE `passenger` ( `passenger_id` varchar(16) NOT NULL, `departure_station` varchar(32) NOT NULL, `arrival_station` varchar(32) NOT NULL, PRIMARY KEY (`passenger_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `train` ( `train_id` varchar(8) NOT NULL, `departure_station` varchar(32) NOT NULL, `arrival_station` varchar(32) NOT NULL, `seat_count` int NOT NULL, PRIMARY KEY (`train_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
赛题数据
为了方便调试,这里保持了一份CSV的输入如下,供测试使用:
相关阅读
-
标题:各类数据库年终总结集中发布;PostgreSQL生态大会于本周六举行;华为云GaussDB/OceanBase不同报告各获第一
重要更新
第七届中国PostgreSQL数据库生态大会于本周六在上海举行,大会邀请了 PostgreSQL 社区相关开发、意见领袖、企业领军人物、资深用户与广大DBA、开发者参加。感兴趣的可以在上海现场参加[13]。
华为云GaussDB,中国金融级分布式数据库市场No.1:根据沙利文联合头豹研究院发布《2024年金融级分布式数据库市场跟踪报告》,华为云GaussDB在2023年、2024年上半年中国金融级分布式数据库市场份额整体规模第一本地部署整体第一[14]
OceanBase 获评分布式数据库市场份额第一:根据工信部赛迪顾问发布 2024 年《中国金融行业数据库市场研究报告》,OceanBase 获评分布式数据库市场份额第一!在银行、保险、证券三大子市场中,OceanBase 同样以综合评分第一的成绩位居榜首[15]
本周是各类总结发布的一周,这里汇总部分供参考:
- 2024 云数据库年度总结[1]
- Databases in 2024: A Year in Review[2]
- 2024年度盘点|阿里云瑶池数据库的高光时刻[3]
- 请回答2024|华为云数据库年度盘点[4]
- 2024 OceanBase 年度报告 | 每一笔记录都算数[6]
- 大事记 | 2024:进而有为 奋力书写万里数据库新篇章[8]
- 步履不停,笃行致远|Greptime 时序之旅 2025 新启程[10]
参考详情
火山云(字节)
AWS(亚马逊云)
- Aurora 现支持 PostgreSQL 16.6、15.10、14.15、13.18 和 12.22等小版本 [9]
腾讯云
参考链接
- [1] https://mp.weixin.qq.com/s/uj9-f3WPLhc-L36CDHIBnA
- [2] https://www.cs.cmu.edu/~pavlo/blog/2025/01/2024-databases-retrospective.html
- [3] https://mp.weixin.qq.com/s/ayT4OwXVEul7jUeyHdBsjw
- [4] https://mp.weixin.qq.com/s/tw-SHBKWW4ZvR0bVqVIGlA
- [5] https://www.volcengine.com/docs/6956/1333447
- [6] https://mp.weixin.qq.com/s/0XImZGhUwi9Gn77q3kD-YQ
- [7] https://www.volcengine.com/docs/6956/1277446
- [8] https://mp.weixin.qq.com/s/RENdrpF1mzTaiqaO_MIMzg
- [9] https://aws.amazon.com/about-aws/whats-new/2024/12/amazon-aurora-postgresql-supports-new-versions/
- [10] https://mp.weixin.qq.com/s/fzuXb1TTM49Nn6I74wgxjw
- [11] https://cloud.tencent.com/document/product/236/42539
- [12] https://cloud.tencent.com/document/product/409/107458
- [13] https://mp.weixin.qq.com/s/Wo6v2dQtqohj9LtYfUPPHQ
- [14] https://mp.weixin.qq.com/s/kPD1AECz4YUmf1mrGr7H4A
- [15] https://mp.weixin.qq.com/s/8a6tVb4TI9UH6Y95DXkdUw
-
在上周五完成最后一个每周行业动态[49]之后,才发现距离春节只剩一个月了,2024也即将过去,要不就整体回顾一下这一整年所看到的数据库领域现状与变化吧。
目录
当红辣子鸡:向量数据库与AI
AI(或者说LLM)所展现的能力,似乎还没有看到上限。而基于此的,面相各行业的应用,也在蓬勃发展。数据库和这个领域最大的结合点,就是向量存储与搜索了。在这一年里,几乎所有的主流数据库,都推出或增强了相关功能。Forrester 也率先发布了独立的 Vector Database Wave ,对各个数据库的向量处理能力做了评估。
Forrester 的 Vector Database Wave
在今年的Q3,Forrester 发布了独立的 Vector Database Wave,其中 Zilliz(对应开源产品名:milvus)凭借完整、高效的向量数据处理能力处于绝对领先的位置。后续厂商分别有:DataStax、Microsoft、Amazon、Oracle、Pinecone 等[30]。
现在,几乎所有的数据库或云厂商也都在投入“向量”技术,这类 AI 技术与应用也都在快速演进,向量存储与应用的竞争还远没有结束。
各个数据库或云厂商发布的 Vector 功能
再过去的2024年,各个数据库厂商都争相发布了自己在向量方向的支持。具体的:
- 百度云发布了独立向量数据库产品: VectorDB[14]
- TiDB 发布了 Vector Search (Beta),将向量能力与 TiDB 做了很好的融合[10]
- pg_vector 发了最新的 0.8 版本,并且在大量的项目中被广泛使用[11]
- Azure 发布自己的向量数据库扩展 DiskANN,并在多个数据库产品中使用[29]
- MariaDB也在11.6版本开始全面支持向量特性[12]
- OceanBase 4.3.3 版本发布,支持了向量数据存储与索引功能[31]
- MySQL 9.0 也开始支持向量存储[13]
- 此外,几乎所有云厂商的 PostgreSQL 产品都支持了 pg_vector 插件
分布式数据库:数据库领域的“金色飞贼”
在整个关系型数据库领域,分布式数据库的市场份额应该是比较有限的。但是,因为其解决的是一个非常困难的问题,而且是大型用户通常需要解决的问题,所以,“分布式数据库”在 RDBMS 领域一直都是一个关键的“组件”。
在中国,主要的分布式数据库厂商有:OceanBase、TiDB、TDSQL、GoldenDB、PolarDB 等,这些产品在一些垂直场景各有优势。在今年07月,IDC发布了《中国金融行业分布式事务型数据库市场份额》报告[7],则很好的反应这个现状:
- 腾讯 TDSQL 获得整体市场份额第一[9]
- OceanBase则获得了金融行业本地部署市场份额第一[8]
- 阿里云在公共云市场的分布式数据库部署,取得第一:参考
- GoldenDB 获得银行本地化部署细分市场第一[33]
从这些数据中,可以看到中国金融行业分布式数据库的情况[7]:整体市场空间约为 2.2 亿美元,同比增长12.1%,其中公共云占比约为30%,专有云占比为70%。
由于 TiDB 更加坚定的开源、国际化策略,在这次数据中并没有看到 PingCAP 去太多的宣传。TiDB的重点则在Cloud、国际化、Vector Serach等方向。
国产数据库与信创
随着全球局势变得更加不确定,对抗思潮的不断推高,更加“自主可控”的基础技术已经变得越来越重要了。在今年的9月,“中国信息安全测评中心”发布了最新一批的安全可靠产品认证名录[36]。相比往期目录,经过认证的数据库数量是增加了,并且额外新增了分布式数据库目录。
本次目录中的集中式数据库
集中式数据库主要厂商有华为 GaussDB、金仓、神通、海量、瀚高、华为 TaurusDB。
本次目录中的分布式数据库
分布式数据库厂商很多,包括了 PingCAP TiDB、达梦、PolarDB、金仓、GBase、神通、虚谷、TDSQL、GaussDB、GoldenDB、OceanBase等。
开源与商业
此外,今年,CockroachDB 对开源协议做出了重大调整,对于使用开源的用户进一步做出限制。具体的,可以理解为对于小企业(年收入1000万美金,似乎也不是很小…)免费使用,年收入超过1000万美金的企业则需要付费[34]。
PostgreSQL在经历一场缓慢的崛起
依旧,PostgreSQL在经历一场缓慢的崛起,MySQL则在原地踏步。在2022年的总结中,就曾给出了这个结论[50],而这个趋势似乎越来越明显。
根据Stack Overflow在2024年的调研数据[51],PostgreSQL已经成为最受开发者欢迎的数据库。在海外,由于云厂商在不断增加对PostgreSQL的投入;在国内,由于国产数据库对于PostgreSQL的青睐。看到,PostgreSQL 崛起的趋势也越来越明显。
虽然,在DB-Engines的排名中,MySQL依旧优势很大,但,过去的一年,MySQL在引入创新版后,发布8.1/2/3/4、9.0版本,但,MySQL最大的进步本身,大概就是版本迭代的模式变化,而这种变化给MySQL创新带来的效果还没有出现。在9.0版本中MySQL虽然发布了
vector
但功能还非常不完善,而 PostgreSQL 的 pg_vector 已经被广为使用了。在由 JetBrains 发布的 《State of Developer Ecosystem Report 2024》[47],也可以看出类似的趋势。该数据显示:
- 从2019到2024年,MySQL 的采用量从60%下降到52%
- 从2019到2024年,PostgreSQL 的采用量从30%增长到45%
数据库领域的融资事件
受大环境的影响,除了AI或大模型相关技术除外,最近两年融资市场都非常冷清。但在过去的2024年,在数据库领域,依旧有非常多的亮眼的融资事件。
达梦数据库上市:首日公开交易,发行价为86.96元/股,当日收盘价为240元(对应市值182亿),盘中最高报313元(参考);而到今年年底,达梦的股价一直 350~400 元上下波动,市值则高达250~300亿之间。
PostgreSQL托管初创公司Tembo获$1400万美元A轮融资[1] ,该公司以开源产品的形式提供完整的PostgreSQL生态产品托管服务,同时提供Tembo Cloud以云产品形式提供托管服务。
OpenAI 收购 实时分析数据库开发商Rockset[2],其估值约为5~10亿美金,以增强大模型在实时数据搜索与分析的能力。
开源时序数据库 GreptimeDB宣布完成数百万美元的新一轮融资。GreptimeDB是一款Rust 语言编写的时序数据库,具有分布式,开源,云原生,兼容性强等特点,帮助企业实时读写、处理和分析时序数据的同时,降低长期存储的成本[5]。
K1 100%收购 MariaDB。K1是最大的小型企业软件投资公司,总部设在加州曼哈顿海滩。MariaDB 是一个独立的 MySQL 分支,由 MySQL 的创始人 Michael Widenius 发起并创建[15]
Supabase 完成C轮8000万美元融资,该平台以PostgreSQL为中心,向开发者提供各类服务的平台,包括数据库、认证服务、存储、实时事件同步、向量数据库等 [24]
ApertureDB 融资$8百万美元,致力于构建AI时代的多模数据流服务,帮助企业更加敏捷的构建上层的智能化的服务[31]
Gartner 的数据库魔力象限
在今年的12月,也就是前几天,Gartner 正式对外发布了 2024 数据库魔力象限[48]。今年,依旧由Google、AWS、Azure、Oracle领跑;MongoDB、DataBricks、Snowflake则又向左上角前进了一些。
阿里云则依旧保持在领导者象限。华为云则在时隔两年后,再次进入该象限。
分布式数据库厂商 SingleStore 进入,而 Yugabyte 跌出。
云厂商们的重要发布
AWS re:Invent 发布新的数据库产品 Aurora DSQL ,提供了跨区域、强一致、多区域读写的能力,同时具备99.999%(多区域部署)的可用性,兼容PostgreSQL;同时发布的还有 DynamoDB 也提供类似的跨区域强一致的能力[6]
阿里云李飞飞发布由Data+AI驱动的多模数据管理平台DMS[22];RDS 产品则主打“降本增效”发布了自研倚天ARM版、集群版、新增了加速 IO 方案(BPE)等,PolarDB 则发布了 Redis 协议接口
字节火山云 veDB MySQL 发布透明HTAP支持,当前处于邀测阶段。采用了MySQL plugin 架构方式,在内核侧自动分流AP 和 TP 请求,如需手动分流,可以使用Proxy[19]。
腾讯云开源了 TXSQL [17]; TDSQL 再创 TPC-DS 世界纪录,以7260万QphDS的性能和37.52元/kQphDS的性价比打破榜单纪录,性能提升282%,成本降低37% [32]
在 Oracle CloudWorld 上,Larry发表主题演讲《Open MultiCloud Era & AI + Cloud Security》,坚定的走多云、私有云战略,发布了Oracle@Google、Oracle@Azure、Oracle@AWS等方案 [16]
GCP的主要发力方向为 AlloyDB;同时也在增强其基础产品的能力,例如 Cloud SQL for SQL Server 现提供两种版本 Enterprise Plus 和 Enterprise。
微软云 Azure 的重点依旧是 Cosmos DB 和 SQL Database。在向量数据库上,Azure多款数据库发布DiskANN向量索引[42]。
OceanBase开发者大会发布 4.3 发版,高调进入实时分析 AP 领域,同时将支持行存 & 列存一体化、新向量化引擎、物化视图等能力,同时宣布了OB Cloud将作为独立的数据库厂商,登陆阿里云精选市场 [23] 。
相比其他数据库,PingCAP 投入了更多资源到 AI (LLM 或者 GenAI,不管叫什么吧)方向上去进行探索,除了支持初步的向量存储之外,TiDB 还可以使用 AutoFlow 和 LlamaIndex 开始的构建一个基于知识库的聊天助手。虽然这个功能与一般意义的“数据库”不那么相关,但是也能看到TiDB的执行力和投入之坚决[35]。
其他
腾讯云 TDSQL 再创 TPC-DS 世界纪录
腾讯云 TDSQL 再创 TPC-DS 世界纪录,以7260万QphDS的性能和37.52元/kQphDS的性价比打破世界纪录,性能提升282%,成本降低37%。通过自研MPP和并行执行框架,TDSQL显著提升资源利用率和计算效率,广泛应用于30多家金融机构 [32]
WeSQL-基于S3的MySQL数据库
这算是一个非常酷的探索。正如 ApeCloud 创始人曹伟所说,“数据库可以做的方向不多”,ApeCloud 在发布了开源数据库的云原生管控平台之后,与今年的11月又开源了一个有意思的产品:WeSQL,基于S3的MySQL数据库。WeSQL 目前属于探索阶段,如果数据存储量很大的测试环境,可以考虑该方案降低存储成本[37]。
AI与数据库的其他结合
此外,其他方向诸如,Text2SQL、数据库优化等方向,也有一些厂商在做更多的探索,例如,AWS Redshift 正式支持(GA)自然语言生成SQL功能,该功能由 Amazon Q 实现,可以再Redshift Query Editor中使用该功能 [21] ,Copilot for Azure新增了对Azure SQL、 Azure Database for MySQL的支持等。
图数据库
图数据库是大模型生态的一部分,在过去的一年,各个图数据库厂商也借势迅速发展。
Neo4j 宣布其年度经常性收入(ARR)已超过2亿美元,并在过去三年中实现了ARR翻番。Neo4j 在快速扩张的图技术市场中的领导地位,以及最近几年图技术对于提升生成式AI(GenAI)结果的准确性、透明性和可解释性至关重要作用,是增长的核心因素[38]。
学术会议与奖项
今年,在主要的数据库学术会议中,国内的厂商也有很多亮眼的表现。包括:
- PolarDB 获得 SIGMOD Industry Track Best Paper 《PolarDB-MP: A Multi-Primary Cloud-Native Database via Disaggregated Shared Memory》[43]。
- 阿里云 PolarDB 获2024年度中国计算机学会(CCF)科技进步一等奖 [41] 。
- KaiwuDB 在国际权威数据库性能测试 benchANT 获得时序数据库场景第一名[46]。
- 基于 openGauss 的项目《面向大型企业规模应用的开源数据库》获得中国通信学会科学技术奖(科技进步类一等奖)[45]。
- 腾讯云 TDSQL 获得深圳市科技进步一等奖[44]。
- 此外,在今年,主要的大数据库厂商在 VLDB/ICDE/SIGMOD等学术会议上均由论文发布。
关于
关于作者
关于作者:周振兴,NineData 联合创始人 & 技术副总裁 ;Oracle ACE ;《高性能MySQL 第三、四版》译者;曾任阿里云数据库资深技术专家
参考链接
- [01] https://tembo.io/blog/series-a
- [02] https://mp.weixin.qq.com/s/-_WFtIhXuOZtWxa0Asthhg
- [03] https://clickhouse.com/blog/clickhouse-welcomes-peerdb-adding-the-fastest-postgres-cdc-to-the-fastest-olap-database
- [04] https://techcrunch.com/2024/07/30/real-time-database-startup-clickhouse-acquires-peerdb-to-expand-its-postgres-support/
- [05] https://mp.weixin.qq.com/s/mHdaa7hzSM0Fpg9oge-idw
- [06] https://aws.amazon.com/rds/aurora/dsql/
- [07] https://www.idc.com/getdoc.jsp?containerId=prCHC52443324
- [08] https://mp.weixin.qq.com/s/3iPKLu3n9qamkjvVW9ugPg
- [09] https://mp.weixin.qq.com/s/fUCRfmLL3tlLNXiDQLSgvw
- [10] https://docs.pingcap.com/tidbcloud/vector-search-overview
- [11] https://github.com/pgvector/pgvector/tags
- [12] https://mariadb.com/kb/en/vector-overview/
- [13] https://dev.mysql.com/doc/refman/9.0/en/vector.html
- [14] https://cloud.baidu.com/product/vdb.html
- [15] https://www.prnewswire.com/news-releases/k1-acquires-mariadb-a-leading-database-software-company-and-appoints-new-ceo-302243508.html
- [16] https://mp.weixin.qq.com/s/rMaAbJgcxfFve26b2cABOA
- [17] https://mp.weixin.qq.com/s/ig9Y0suO50j1jg6kRD42tg
- [18] http://static.cninfo.com.cn/finalpage/2024-08-31/1221078114.PDF
- [19] https://www.volcengine.com/docs/6357/1314096
- [20] https://docs.percona.com/everest/index.html
- [21] https://aws.amazon.com/about-aws/whats-new/2024/09/amazon-q-generative-sql-amazon-redshift/
- [22] https://mp.weixin.qq.com/s/igmMR0CFLZXjKjuA4bZ-lA
- [23] https://mp.weixin.qq.com/s/_MPJf9BOaK_2cV5jz__0qg
- [24] https://supabase.com/
- [25] https://github.com/redis/redis/commit/0b34396924eca4edc524469886dc5be6c77ec4ed
- [26] https://www.linuxfoundation.org/press/linux-foundation-launches-open-source-valkey-community
- [27] https://aws.amazon.com/about-aws/whats-new/2024/10/amazon-elasticache-valkey/
- [28] https://help.aliyun.com/zh/polardb/polardb-for-mysql/user-guide/orca-overview
- [29] https://azure.microsoft.com/en-us/updates/v2/DiskANN-indexing-on-Azure-Database-for-PostgreSQL
- [30] https://mp.weixin.qq.com/s/Sq1GUOTP02QWBOqS0N3A9A
- [31] https://siliconangle.com/2024/10/10/aperturedata-raises-8-5m-multimodal-ai-database/
- [32] https://mp.weixin.qq.com/s/uJHPSoAhOSBYG8OYq8hvvQ
- [33] https://mp.weixin.qq.com/s/Zm7ODdZWWIJuiQIpqat23g
- [34] https://www.reddit.com/r/devops/comments/1eytts3/cockroachdb_is_changing_license_again_moving_to/?rdt=33989
- [35] https://mp.weixin.qq.com/s/epJuxnz7z8vjE-Axazbdog
- [36] http://www.itsec.gov.cn/aqkkcp/cpgg/202409/t20240930_194299.html
- [37] https://wesql.io/
- [38] https://neo4j.com/press-releases/neo4j-revenue-milestone-2024/
- [39] https://www.nebula-graph.io/posts/nebulagraph_enterprise_5.0_gql_support
- [40] https://www.iso.org/standard/76120.html
- [41] https://mp.weixin.qq.com/s/kWpa-yP3DDfJHcdII4qV0Q
- [42] https://ignite.microsoft.com/en-US/home
- [43] https://2024.sigmod.org/sigmod_awards.shtml
- [44] https://cloud.tencent.com/developer/article/2419668
- [45] https://www.china-cic.cn/Detail/24/5924/5924
- [46] https://mp.weixin.qq.com/s/p_7iKRwPTK6kXxRy0XQxqQ
- [47] https://www.jetbrains.com/lp/devecosystem-2024/#db6
- [48] https://www.orczhou.com/index.php/2024/12/2024-gartner-cloud-database-magic-quadrant/
- [49] https://www.orczhou.com/index.php/what-is-new-database-archive/
- [50] https://www.orczhou.com/index.php/2022/12/what-is-new-database-summary-2022/
- [51] https://survey.stackoverflow.co/2024/technology#most-popular-technologies
- [52] https://support.huaweicloud.com/usermanual-gaussdbformysql/gaussdbformysql_03_0080.html
-
CTE 的全称是 Common Table Expressions,有时候也缩写为 CTEs,在 SQL 标准中使用
WITH clause
表达[1]。在 ANSI standard 标准的 SQL:1999 中定义,参考:ISO/IEC 9075-2:1999[7][8]。该特性支持在 MySQL 8.0 中正式被引入[9]。通常,如果不需要编写特别复杂的 SQL ,更为具体的,如果无需编写带有递归功能的 SQL 时,CTE 通常都不是必须的。所以,该语法功能,对于分析型的场景或者数据开发等场景更为常用。
这里概述 CTE 的两个常用功能:简化子查询结构、实现递归执行。其中,实现递归执行,是 CTE 独特的能力,是对 SQL 能力的一种补充。
简化子查询结构
CTE 功能提供了一种新的子查询或临时表的写法。可以把后续 SQL 中需要使用的临时表或子查询独立的放在
WITH
子句中,使得结构看起来更为清晰一些。例如,如下两个写法在语义上是等价的:
WITH t_mid as ( SELECT * FROM t_1 JOIN t_2 ) SELECT * FROM t_mid;
SELECT * FROM ( SELECT * FROM t_1 JOIN t_2 ) t_mid;
递归执行
该功能是 CTE 的核心能力。例如,在表中查询好友的好友、查询主管的主管、递归数据生成等功能。
递归执行方式概述
在各个数据库的关于 CTE 介绍都会较为详细的描述,这里对该执行过程做一个简单的描述。
对于递归 CTE 对应的 SQL 总是有类似结构:
WITH RECURSIVE table_cte (col_01 ... ) AS ( SELECT ... -- return initial row set UNION ALL SELECT ... FROM table_cte -- return additional row sets )
在该结构中:
- 第一个
SELECT
为数据初始化 - 第二个
SELECT
则为递归执行的部分,该部分在引用表table_cte
时,总是使用该表的最后一条生成记录
斐波那契数列的生成
这是一个MySQL文档中的示例[10]。
WITH RECURSIVE fibonacci (n, fib_n, next_fib_n) AS ( SELECT 1, 0, 1 UNION ALL SELECT n + 1, next_fib_n, fib_n + next_fib_n FROM fibonacci WHERE n < 10 ) SELECT * FROM fibonacci; +------+-------+------------+ | n | fib_n | next_fib_n | +------+-------+------------+ | 1 | 0 | 1 | | 2 | 1 | 1 | | 3 | 1 | 2 | | 4 | 2 | 3 | | 5 | 3 | 5 | ... | 10 | 34 | 55 | +------+-------+------------+
根据前面小结“递归执行方式概述”中所描述,在每次执行上述的第二个
SELECT
,即SELECT n + 1, next_fib_n, fib_n + next_fib_n FROM fibonacci
时,总是使用 CTE 表fibonacci
最后生成的记录。所以,该SQL中引用的列n
、fib_n
和next_fib_n
均为表fibonacci
最后生成的记录,即上一条记录。所以,该 SQL 第一列返回的是一个自增的序列;第二列,即为斐波那契数列;第三列是用于计算的中间列。
广度优先还是深度优先
在 MySQL 的递归遍历中,并没有像 PostgreSQL 那样可以通过 SQL 关键字去控制。在文档中,也没有描述是广度优先还是深度优先。这里通过一些 SQL 变量来看看,到底是广度优先还是深度优先。
先说结论吧:MySQL 使用的是广度优先遍历。关注验证过程的,可以继续阅读这一段的示例和解读。
这里使用 MySQL 官方文档中另一个示例。该示例包含一个企业人员组织架构的关系:
CREATE TABLE employees ( id INT PRIMARY KEY NOT NULL, name VARCHAR(100) NOT NULL, manager_id INT NULL, INDEX (manager_id), FOREIGN KEY (manager_id) REFERENCES employees (id) ); INSERT INTO employees VALUES (333, "Yasmina", NULL), # Yasmina is the CEO (manager_id is NULL) (198, "John", 333), # John has ID 198 and reports to 333 (Yasmina) (692, "Tarek", 333), (29, "Pedro", 198), (4610, "Sarah", 29), (72, "Pierre", 29), (123, "Adil", 692);
我们可以使用如下的递归 CTE 语句进行组织架构遍历:
WITH RECURSIVE employee_paths (id, name, path) AS ( SELECT id, name, CAST(id AS CHAR(200)) FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.name, CONCAT(ep.path, ',', e.id) FROM employee_paths AS ep JOIN employees AS e ON ep.id = e.manager_id ) SELECT * FROM employee_paths ORDER BY path;
我们对这条 SQL 语句进行如下修改,以观察是广度优先还是深度优先:
WITH RECURSIVE employee_paths (id, name, path,seq) AS ( SELECT id, name, CAST(id AS CHAR(200)),@seq:=1 FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.name, CONCAT(ep.path, ',', e.id),@seq:=@seq+1 FROM employee_paths AS ep JOIN employees AS e ON ep.id = e.manager_id ) SELECT * FROM employee_paths ORDER BY seq; +------+---------+-----------------+------+ | id | name | path | seq | +------+---------+-----------------+------+ | 333 | Yasmina | 333 | 1 | | 198 | John | 333,198 | 2 | | 692 | Tarek | 333,692 | 3 | | 29 | Pedro | 333,198,29 | 4 | | 123 | Adil | 333,692,123 | 5 | | 72 | Pierre | 333,198,29,72 | 6 | | 4610 | Sarah | 333,198,29,4610 | 7 | +------+---------+-----------------+------+
该结果的最后一列 seq,反映了输出的先后顺序。可以看到,这里使用的广度优先遍历。
参考链接
- [1] 15.2.20 WITH (Common Table Expressions)@MySQL
- [2] 7.8. WITH Queries (Common Table Expressions)@PostgreSQL
- [3] WITH common_table_expression (Transact-SQL)@SQL Server
- [4] subquery_factoring_clause@Oracle Documentatioin
- [5] Hierarchical and recursive queries in SQL@Wikipedia
- [6] Common table expressions for ANSI SQL@AWS Documentation
- [7] https://www.iso.org/standard/26197.html
- [8] Information technology — Database languages — SQL — Part 2: Foundation (SQL/Foundation)
- [9] https://dev.mysql.com/blog-archive/whats-new-in-mysql-8-0-generally-available/
- [10] https://dev.mysql.com/doc/refman/8.4/en/with.html#common-table-expressions-recursive-fibonacci-series
- 第一个