适用于 SQL Server 2008-2012、PostgreSQL 9.1.9、Oracle 11g 的解决方案
实际上,递归 CTE 是几乎所有当前 RDBMS 的解决方案,包括 PostgreSQL(解释和示例如下所示)。但是,对于 Oracle DB,还有另一种更好的解决方案(优化):分层查询。
NOCYCLE 指示 Oracle 返回行,即使您的数据中有循环。
CONNECT_BY_ROOT 使您可以访问根元素,甚至是查询中的几层。
使用 HR 模式:
Oracle 11g 对应的代码:
select
b.id_bus_line, b.id_bus_stop
from BusLine_BusStop b
start with b.is_first_stop = 1
connect by nocycle prior b.id_next_bus_stop = b.id_bus_stop and prior b.id_bus_line = b.id_bus_line
Oracle 11g 的演示(我自己的代码)。
请注意,标准是 SQL:1999 规范中的递归 CTE。如您所见,SQL Server 和 PostgreSQL 之间存在一些差异。
以下解决方案适用于 SQL Server 2012:
;WITH route AS
(
SELECT BusLineId, BusStopId, NextBusStopId
FROM BusLine_BusStop
WHERE IsFirstStop = 1
UNION ALL
SELECT b.BusLineId, b.BusStopId, b.NextBusStopId
FROM BusLine_BusStop b
INNER JOIN route r
ON r.BusLineId = b.BusLineId
AND r.NextBusStopId = b.BusStopId
WHERE IsFirstStop = 0 or IsFirstStop is null
)
SELECT BusLineId, BusStopId
FROM route
ORDER BY BusLineId
SQL Server 2012 的演示(受 TI 启发)。
这个适用于 PostgreSQL 9.1.9(它不是最佳的,但应该可以工作):
诀窍在于为您可以重置的当前会话创建一个专用的临时序列。
create temp sequence rownum;
WITH final_route AS
(
WITH RECURSIVE route AS
(
SELECT BusLineId, BusStopId, NextBusStopId
FROM BusLine_BusStop
WHERE IsFirstStop = 1
UNION ALL
SELECT b.BusLineId, b.BusStopId, b.NextBusStopId
FROM BusLine_BusStop b
INNER JOIN route r
ON r.BusLineId = b.BusLineId
AND r.NextBusStopId = b.BusStopId
WHERE IsFirstStop = 0 or IsFirstStop is null
)
SELECT BusLineId, BusStopId, nextval('rownum') as rownum
FROM route
)
SELECT BusLineId, BusStopId
FROM final_route
ORDER BY BusLineId, rownum;
我自己的 PostgreSQL 9.1.9 的演示。
编辑:
很抱歉进行了多次编辑。通过子记录而不是其父记录连接记录是非常罕见的。您可以通过删除 isFirstStop 列并使用 id_PreviousBusStop 列(如果可能)连接您的记录来避免这种糟糕的表示。在这种情况下,您必须将第一条记录的 id_PreviousBusStop 设置为 null。您可以节省空间(对于固定长度的数据,仍然保留整个空间)。此外,您的查询将使用更少的字符变得更有效率。