暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

从围棋收官到秦楚大战的数据库SQL实现(中)

原创 收获不止数据库 2024-09-18
106


上集回顾 从围棋收官到秦楚大战的数据库SQL实现(上)

昔秦孝公以SQL神技,推演秦楚争地之策,二公子谨记围棋收官之法:双先、单先、逆收、后手,皆心领神会。

然,收官之道,非止于先后,更须计城池之广狭。此中奥妙,待我慢慢道来。

为便于读者理解,特将上集描述的围棋收官术语、收官相类比的城池之争再列于此。
收官术语名词解释
单先己方的关键点,占据后对方必须立即应对,否则将遭受损失。
双先双方都认为紧急且必须立即应对的点,无论谁下都会迫使对方回应。
后手对方可以不立即应对的着手。
逆收抢占对方的单先点,阻止对方获得先手权。


:1. 本文代码左右滑动观看;2. 建表SQL语句出现size 和type字段,此为Oracle关键字,如果在Oracle中执行需要加引号,考虑到排版等问题,这里就略去了。








1



城池广狭,胜于多寡

秦孝公:SQL语言确实厉害,真得感谢那位授寡人SQL神功的恩公,不知何日能再得见一面。
商鞅:我也盼能一见,当面请教。

秦孝公:嗯。对了,驷儿、华儿,围棋的每一手棋并非一样大,城池亦如此,若考虑城池大小,攻城之策又当如何?

嬴华:父王,是否应在原先双先、秦先手、楚先手、后手的顺序基础上,再按城池大小排序。

秦孝公:吾儿聪明,我们改造先前的建表语句,增加字段SIZES,以表示城池的大小,如下:

-- 创建城池表
CREATE TABLE cities (
id NUMBER(
5) PRIMARY KEY,
name VARCHAR2(
50),
types VARCHAR2(
10),
sizes NUMBER(
5)
);
-- 插入城池数据,包括大小
INSERT INTO cities (id, name, types, sizes) VALUES (1, '平原1', '双先', 80);
INSERT INTO cities (id, name, types, sizes) VALUES (2, '平原2', '双先', 75);
INSERT INTO cities (id, name, types, sizes) VALUES (3, '草原1', '秦先手', 70);
INSERT INTO cities (id, name, types, sizes) VALUES (4, '草原2', '秦先手', 65);
INSERT INTO cities (id, name, types, sizes) VALUES (5, '山水1', '楚先手', 60);
INSERT INTO cities (id, name, types, sizes) VALUES (6, '山水2', '楚先手', 85);
INSERT INTO cities (id, name, types, sizes) VALUES (7, '高原1', '后手', 55);
INSERT INTO cities (id, name, types, sizes) VALUES (8, '高原2', '后手', 180);


若按原先顺序不考虑大小,秦将获得6座城池(平原1、平原2、草原1、草原2、山水1、高原2),楚则得2座城池(山水2、高原1)。结果如下,吾儿还记得否?

嬴驷和嬴华:记得。

秦孝公:既知城池大小,且算算秦楚各占领多大面积?

嬴华:秦占得城池为:平原1(80)、平原2(75)、草原1(70)、草原2(65)、山水1(60)、高原2(180),合计555。楚占得城池为:山水2(85)、高原1(55),合计140。秦比楚多占415,如下表所示。





2



量地制谋,攻取有方

秦孝公:吾儿,若以城池面积为目标,秦楚攻城之策可有不妥?
嬴驷:父王,儿以为秦楚双方均有失误,秦当先取面积更大的山水2而非山水1,楚应占面积更大的高原2而非高原1。这样秦得430,楚占240,见下表。

秦孝公:吾儿所言甚是,来,且让我用SQL神功验证一番。

WITH capture_process AS (
SELECT
c.id, c.name, c.types, c.sizes,
CASE
WHEN c.types = '双先' THEN '秦'
WHEN c.types = '秦先手' THEN '秦'
WHEN c.types = '楚先手' THEN
CASE
WHEN MOD(ROW_NUMBER() OVER (PARTITION BY c.types ORDER BY c.sizes DESC), 2) = 1 THEN '秦'
ELSE '楚'
END
WHEN c.types = '后手' THEN
CASE
WHEN MOD(ROW_NUMBER() OVER (PARTITION BY c.types ORDER BY c.sizes DESC), 2) = 1 THEN '楚'
ELSE '秦'
END
END AS captured_by,
CASE
WHEN c.types = '双先' THEN 1
WHEN c.types = '秦先手' THEN 2
WHEN c.types = '楚先手' THEN 3
WHEN c.types = '后手' THEN 4
END AS type_order,
ROW_NUMBER() OVER (PARTITION BY c.types ORDER BY c.sizes DESC) AS size_rank
FROM cities c
),
ordered_cities AS (
SELECT
captured_by,
name || ' ('|| types || ', 大小: '|| TO_CHAR(sizes) || ')' AS city_info,
type_order,
CASE
WHEN types IN ('楚先手', '后手') THEN size_rank
ELSE 0
END AS sub_order,
sizes -- 保留sizes字段,用于后续计算总面积
FROM capture_process
)
SELECT
captured_by AS category,
COUNT(*) AS count,
SUM(sizes) AS total_size, -- 新增:计算每个国家占领城池的总面积
LISTAGG(city_info, ', ') WITHIN GROUP (
ORDER BY type_order, sub_order, sizes DESC
) AS cities
FROM ordered_cities
GROUP BY captured_by
ORDER BY
CASE WHEN captured_by = '秦' THEN 1 ELSE 2 END,
COUNT(*) DESC

SQL 核心逻辑说明:

  1. capture_process 子查询
    此查询依据城池类型与大小,推断各城归属。规则为:
         a. 双先和秦先手的城池由秦直接占领。
         b. 楚先手之城以大小为序,交替分配给秦楚。通过ROW_NUMBER() 函数对大小排名,并通过MOD() 函数将其分配给两国。
          c. 后手的城池同样依据大小顺序交替分配。
  2. ordered_cities 子查询
    此部分对城池按类型顺序及大小进行排序,并为最终结果展示做准备。这里使用了 LISTAGG() 进行字符串聚合,输出每一方占领的城池详情。

  3. 最终查询

    据城池归属聚合数据,列出秦楚各得城池,依大小排序呈现结果。
输出结果:
秦6城,秦疆域430;楚2城,楚疆域240,如嬴驷所料。

商鞅:大王的SQL可真厉害,围棋正是如此对弈,恭喜两位公子学有所成!




3



攻伐之序,尚可再优

秦楚大战很快打响,最终秦得六城,疆域共计430。而楚占两城,总面积为240,果真如众人所料。

数月后,秦孝公复邀商鞅对弈。七局结束,商鞅竟零比七惨败,大为震惊。

秦孝公:商君,心服否?寡人近得高人指点,棋艺大进。诸位容我引荐,此乃来自两千年之后的围棋职业五段高手,胡傲华老师。

胡傲华:诸位好,商君这几局实际是输在收官上。

商鞅:收官?我等可刚总结过收官策略,还据此在八城之争中取得显著成果

胡傲华:商君,八城之争其实还有提升空间。

商鞅、嬴驷、嬴华闻言,更觉困惑。

秦孝公:胡老师所言不虚。我大秦应该改抢山水2,而非高原2。

商鞅:不可,如此我军仅得五城,非六城矣。

嬴华:然也。若不逆收,楚军将连取山水1、山水2,再得高原1,共三城。我军仅得五城

胡傲华:诸位不妨再细想一番。

嬴驷:哦,高原2面积180,即便我军只得五城,总疆域仍会增加

嬴华:确实如此!我略作计算,秦军五城疆域可达470,楚军三城仅为200。我军将较楚军多出270之地(而非之前的190),见表如下。

商鞅:妙哉!270-190=80,攻城顺序一变,我大秦竟可多得80土地!

秦孝公:围棋收官亦是如此。寡人曾弃逆收而取后手,商君可曾注意?

商鞅:想起来了,大王高明!

胡傲华:一般而言,逆收优先于后手。然有一例外:若后手价值逾逆收两倍,可优先取之。

嬴驷:懂了,楚先手山水2面积为85,倍之也仅170,而后手的高原2面积为180,超山水2两倍有余,故优先取之。

胡傲华:驷公子慧眼如炬,围棋收官正合此理。我在先前诸位梳理的流程图上略作改进,若言之前图示为业余3段水平,新图则已达业余4段之境。如下所示,请诸位反复细品,定有收获。






4



秦公展技,尽显神功

秦孝公:诸位,胡老师教我等,后手大于逆收两倍者可先取。今寡人以SQL神功演之,看结果如何。

WITH city_data AS (
SELECT
id, name, types, sizes,
CASE
WHEN types = '双先' THEN 1
WHEN types = '秦先手' THEN 2
WHEN types = '楚先手' THEN 3
WHEN types = '后手' THEN 4
END AS type_order,
ROW_NUMBER() OVER (PARTITION BY types ORDER BY sizes DESC) AS size_rank
FROM cities
),
strategic_choice AS (
SELECT
id,name,types,sizes,type_order,size_rank,
MAX(CASE WHEN types = '后手' THEN sizes ELSE 0 END) OVER () AS max_后手_size,
MAX(CASE WHEN types = '楚先手' THEN sizes ELSE 0 END) OVER () AS max_楚先手_size
FROM city_data
),
capture_process AS (
SELECT
id, name, types, sizes, type_order, size_rank,
CASE
WHEN types IN ('双先', '秦先手') THEN '秦'
WHEN types = '后手' AND sizes = max_后手_size
AND sizes > 2 * max_楚先手_size THEN '秦'
WHEN types = '楚先手' AND
NOT EXISTS (SELECT 1 FROM strategic_choice
WHERE types = '后手' AND sizes > 2 * max_楚先手_size) THEN '秦'
ELSE '楚'
END AS captured_by
FROM strategic_choice
)
SELECT
captured_by
AS category,
COUNT(*) AS count,
SUM(sizes) AS total_size,
LISTAGG(name || ' ('|| types || ', 大小: '|| TO_CHAR(sizes) || ')', ', ')
WITHIN GROUP (ORDER BY type_order, sizes DESC) AS cities
FROM capture_process
GROUP BY captured_by
ORDER BY
CASE WHEN captured_by = '秦' THEN 1 ELSE 2 END;

SQL 核心逻辑说明(突出后手价值大于逆收两倍的实现):

  1. 在strategic_choice子查询中,我们计算了关键值:

  2. MAX(CASE WHEN types = '后手' THEN sizes ELSE 0 END) OVER () AS max_后手_size,
    MAX(CASE WHEN types = '楚先手' THEN sizes ELSE 0 END) OVER () AS max_楚先手_size

    这里计算了最大的后手城池大小和最大的楚先手城池大小。

  3. 在capture_process子查询中,关键的判断逻辑如下

  4. CASE
    WHEN types = '后手' AND sizes = max_后手_size
    AND sizes > 2 * max_楚先手_size THEN '秦'
    WHEN types = '楚先手' AND
    NOT EXISTS (SELECT 1 FROM strategic_choice
    WHERE types = '后手' AND sizes > 2 * max_楚先手_size) THEN '秦'
    ELSE '楚'
    END AS captured_by

这个CASE语句实现了核心逻辑:

  • 如果是后手城池,且其大小等于最大后手城池大小,并且大于最大楚先手城池大小的两倍,则秦国占领。

  • 如果是楚先手城池,但不存在大于楚先手两倍的后手城池,则秦国占领。

  • 其他情况下,楚国占领。

  • 这种实现方式确保了当后手城池的价值超过楚先手城池两倍时,秦国会优先选择后手城池,而不是按常规顺序选择楚先手城池。

输出结果:
秦占5城,总疆域470;楚夺3城,共计200。与我等所料一致

商鞅:我悟了!大王,可再战一局?

秦孝公欣然应战,三百回合后,商鞅再败。
商鞅:臣何以再败?
秦孝公:商君啊,胡老师所授,岂止于此?方才八城之争,尚非最优。
商鞅闻言,惊愕转向胡傲华。
胡傲华:诚如大王所言,关键在于,若秦占双先城1,楚是否当置之不理,直取双先城2。
商鞅:置之不理,岂不遭罚?
胡傲华:楚若占双先城2而秦不应之,则秦同样会遭受惩罚。关键在于精确计算双方可能之损失,从而定最优策,此理同适用于围棋。每逢大王弈出双先,您必应之,从未反击,此乃败因所在
商鞅:原来如此!受教了。
秦孝公:哈哈,商君,今可心服否?
就在此时,一个声音远远传来:大王,别来无恙。
秦孝公:恩公?正是传我SQL神功的恩公,他来了!




......未完待续


致谢
本集得到了许多专家的帮忙:有围棋职业五段高手胡傲华,有数据库专家明玉琢、司马辽太杰王青牛(青牛踏雪)等 ,给了我很多宝贵意见,非常感谢~!
本集的代码为复杂的Oracle SQL,依次在达梦、Oceanbase、虚谷、金乌这四家自主原创据库环境执行,发现皆可轻松运行,厉害,为国产数据库点赞!




更多精彩原创内容见公众号
    点关注     不迷路     

往期回顾,欢迎留言与转发




预告:《超融合数据库》即将出版




最后修改时间:2025-08-05 14:48:19
文章转载自收获不止数据库,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论