1. ---------------------------------------------------------------------
  2. -- Inside Microsoft SQL Server 2008: T-SQL Querying (MSPress, 2009)
  3. -- Chapter 12 - Graphs, Trees, Hierarchies and Recursive Queries
  4. -- Copyright Itzik Ben-Gan, 2009
  5. -- All Rights Reserved
  6. ---------------------------------------------------------------------
  7.  
  8. ---------------------------------------------------------------------
  9. -- Scenarios
  10. ---------------------------------------------------------------------
  11.  
  12. ---------------------------------------------------------------------
  13. -- Employees Organizational Chart
  14. ---------------------------------------------------------------------
  15.  
  16. -- Listing 12-1: DDL & Sample Data for Employees
  17. SET NOCOUNT ON;
  18. USE tempdb;
  19. GO
  20. IF OBJECT_ID('dbo.Employees') IS NOT NULL
  21. DROP TABLE dbo.Employees;
  22. GO
  23. CREATE TABLE dbo.Employees
  24. (
  25. empid INT NOT NULL PRIMARY KEY,
  26. mgrid INT NULL REFERENCES dbo.Employees,
  27. empname VARCHAR(25) NOT NULL,
  28. salary MONEY NOT NULL,
  29. CHECK (empid <> mgrid)
  30. );
  31.  
  32. INSERT INTO dbo.Employees(empid, mgrid, empname, salary) VALUES
  33. (1, NULL, 'David' , $10000.00),
  34. (2, 1, 'Eitan' , $7000.00),
  35. (3, 1, 'Ina' , $7500.00),
  36. (4, 2, 'Seraph' , $5000.00),
  37. (5, 2, 'Jiru' , $5500.00),
  38. (6, 2, 'Steve' , $4500.00),
  39. (7, 3, 'Aaron' , $5000.00),
  40. (8, 5, 'Lilach' , $3500.00),
  41. (9, 7, 'Rita' , $3000.00),
  42. (10, 5, 'Sean' , $3000.00),
  43. (11, 7, 'Gabriel', $3000.00),
  44. (12, 9, 'Emilia' , $2000.00),
  45. (13, 9, 'Michael', $2000.00),
  46. (14, 9, 'Didi' , $1500.00);
  47.  
  48. CREATE UNIQUE INDEX idx_unc_mgrid_empid ON dbo.Employees(mgrid, empid);
  49. GO
  50.  
  51. ---------------------------------------------------------------------
  52. -- Bill Of Materials (BOM)
  53. ---------------------------------------------------------------------
  54.  
  55. -- Listing 12-2: DDL & Sample Data for Parts, BOM
  56. SET NOCOUNT ON;
  57. USE tempdb;
  58. GO
  59. IF OBJECT_ID('dbo.BOM') IS NOT NULL
  60. DROP TABLE dbo.BOM;
  61. GO
  62. IF OBJECT_ID('dbo.Parts') IS NOT NULL
  63. DROP TABLE dbo.Parts;
  64. GO
  65. CREATE TABLE dbo.Parts
  66. (
  67. partid INT NOT NULL PRIMARY KEY,
  68. partname VARCHAR(25) NOT NULL
  69. );
  70.  
  71. INSERT INTO dbo.Parts(partid, partname) VALUES
  72. ( 1, 'Black Tea' ),
  73. ( 2, 'White Tea' ),
  74. ( 3, 'Latte' ),
  75. ( 4, 'Espresso' ),
  76. ( 5, 'Double Espresso'),
  77. ( 6, 'Cup Cover' ),
  78. ( 7, 'Regular Cup' ),
  79. ( 8, 'Stirrer' ),
  80. ( 9, 'Espresso Cup' ),
  81. (10, 'Tea Shot' ),
  82. (11, 'Milk' ),
  83. (12, 'Coffee Shot' ),
  84. (13, 'Tea Leaves' ),
  85. (14, 'Water' ),
  86. (15, 'Sugar Bag' ),
  87. (16, 'Ground Coffee' ),
  88. (17, 'Coffee Beans' );
  89.  
  90. CREATE TABLE dbo.BOM
  91. (
  92. partid INT NOT NULL REFERENCES dbo.Parts,
  93. assemblyid INT NULL REFERENCES dbo.Parts,
  94. unit VARCHAR(3) NOT NULL,
  95. qty DECIMAL(8, 2) NOT NULL,
  96. UNIQUE(partid, assemblyid),
  97. CHECK (partid <> assemblyid)
  98. );
  99.  
  100. INSERT INTO dbo.BOM(partid, assemblyid, unit, qty) VALUES
  101. ( 1, NULL, 'EA', 1.00),
  102. ( 2, NULL, 'EA', 1.00),
  103. ( 3, NULL, 'EA', 1.00),
  104. ( 4, NULL, 'EA', 1.00),
  105. ( 5, NULL, 'EA', 1.00),
  106. ( 6, 1, 'EA', 1.00),
  107. ( 7, 1, 'EA', 1.00),
  108. (10, 1, 'EA', 1.00),
  109. (14, 1, 'mL', 230.00),
  110. ( 6, 2, 'EA', 1.00),
  111. ( 7, 2, 'EA', 1.00),
  112. (10, 2, 'EA', 1.00),
  113. (14, 2, 'mL', 205.00),
  114. (11, 2, 'mL', 25.00),
  115. ( 6, 3, 'EA', 1.00),
  116. ( 7, 3, 'EA', 1.00),
  117. (11, 3, 'mL', 225.00),
  118. (12, 3, 'EA', 1.00),
  119. ( 9, 4, 'EA', 1.00),
  120. (12, 4, 'EA', 1.00),
  121. ( 9, 5, 'EA', 1.00),
  122. (12, 5, 'EA', 2.00),
  123. (13, 10, 'g' , 5.00),
  124. (14, 10, 'mL', 20.00),
  125. (14, 12, 'mL', 20.00),
  126. (16, 12, 'g' , 15.00),
  127. (17, 16, 'g' , 15.00);
  128. GO
  129.  
  130. ---------------------------------------------------------------------
  131. -- Road System
  132. ---------------------------------------------------------------------
  133.  
  134. -- Listing 12-3: DDL & Sample Data for Cities, Roads
  135. SET NOCOUNT ON;
  136. USE tempdb;
  137. GO
  138. IF OBJECT_ID('dbo.Roads') IS NOT NULL
  139. DROP TABLE dbo.Roads;
  140. GO
  141. IF OBJECT_ID('dbo.Cities') IS NOT NULL
  142. DROP TABLE dbo.Cities;
  143. GO
  144.  
  145. CREATE TABLE dbo.Cities
  146. (
  147. cityid CHAR(3) NOT NULL PRIMARY KEY,
  148. city VARCHAR(30) NOT NULL,
  149. region VARCHAR(30) NULL,
  150. country VARCHAR(30) NOT NULL
  151. );
  152.  
  153. INSERT INTO dbo.Cities(cityid, city, region, country) VALUES
  154. ('ATL', 'Atlanta', 'GA', 'USA'),
  155. ('ORD', 'Chicago', 'IL', 'USA'),
  156. ('DEN', 'Denver', 'CO', 'USA'),
  157. ('IAH', 'Houston', 'TX', 'USA'),
  158. ('MCI', 'Kansas City', 'KS', 'USA'),
  159. ('LAX', 'Los Angeles', 'CA', 'USA'),
  160. ('MIA', 'Miami', 'FL', 'USA'),
  161. ('MSP', 'Minneapolis', 'MN', 'USA'),
  162. ('JFK', 'New York', 'NY', 'USA'),
  163. ('SEA', 'Seattle', 'WA', 'USA'),
  164. ('SFO', 'San Francisco', 'CA', 'USA'),
  165. ('ANC', 'Anchorage', 'AK', 'USA'),
  166. ('FAI', 'Fairbanks', 'AK', 'USA');
  167.  
  168. CREATE TABLE dbo.Roads
  169. (
  170. city1 CHAR(3) NOT NULL REFERENCES dbo.Cities,
  171. city2 CHAR(3) NOT NULL REFERENCES dbo.Cities,
  172. distance INT NOT NULL,
  173. PRIMARY KEY(city1, city2),
  174. CHECK(city1 < city2),
  175. CHECK(distance > 0)
  176. );
  177.  
  178. INSERT INTO dbo.Roads(city1, city2, distance) VALUES
  179. ('ANC', 'FAI', 359),
  180. ('ATL', 'ORD', 715),
  181. ('ATL', 'IAH', 800),
  182. ('ATL', 'MCI', 805),
  183. ('ATL', 'MIA', 665),
  184. ('ATL', 'JFK', 865),
  185. ('DEN', 'IAH', 1120),
  186. ('DEN', 'MCI', 600),
  187. ('DEN', 'LAX', 1025),
  188. ('DEN', 'MSP', 915),
  189. ('DEN', 'SEA', 1335),
  190. ('DEN', 'SFO', 1270),
  191. ('IAH', 'MCI', 795),
  192. ('IAH', 'LAX', 1550),
  193. ('IAH', 'MIA', 1190),
  194. ('JFK', 'ORD', 795),
  195. ('LAX', 'SFO', 385),
  196. ('MCI', 'ORD', 525),
  197. ('MCI', 'MSP', 440),
  198. ('MSP', 'ORD', 410),
  199. ('MSP', 'SEA', 2015),
  200. ('SEA', 'SFO', 815);
  201. GO
  202.  
  203. ---------------------------------------------------------------------
  204. -- Iterations/Recursion
  205. ---------------------------------------------------------------------
  206.  
  207. ---------------------------------------------------------------------
  208. -- Subordinates
  209. ---------------------------------------------------------------------
  210.  
  211. -- Creation Script for Function Subordinates1
  212.  
  213. ---------------------------------------------------------------------
  214. -- Function: Subordinates1, Descendants
  215. --
  216. -- Input : @root INT: Manager id
  217. --
  218. -- Output : @Subs Table: id and level of subordinates of
  219. -- input manager (empid = @root) in all levels
  220. --
  221. -- Process : * Insert into @Subs row of input manager
  222. -- * In a loop, while previous insert loaded more than 0 rows
  223. -- insert into @Subs next level of subordinates
  224. ---------------------------------------------------------------------
  225. USE tempdb;
  226. GO
  227. IF OBJECT_ID('dbo.Subordinates1') IS NOT NULL
  228. DROP FUNCTION dbo.Subordinates1;
  229. GO
  230. CREATE FUNCTION dbo.Subordinates1(@root AS INT) RETURNS @Subs Table
  231. (
  232. empid INT NOT NULL PRIMARY KEY NONCLUSTERED,
  233. lvl INT NOT NULL,
  234. UNIQUE CLUSTERED(lvl, empid) -- Index will be used to filter level 按级数筛选时用的索引
  235. )
  236. AS
  237. BEGIN
  238. DECLARE @lvl AS INT = 0; -- Initialize level counter with 0 初始化计数器为0
  239.  
  240. -- Insert root node to @Subs
  241. INSERT INTO @Subs(empid, lvl)
  242. SELECT empid, @lvl FROM dbo.Employees WHERE empid = @root;
  243.  
  244. WHILE @@rowcount > 0 -- while previous level had rows 当上一级存在行
  245. BEGIN
  246. SET @lvl = @lvl + 1; -- Increment level counter 递增级数计数器
  247.  
  248. -- Insert next level of subordinates to @Subs
  249. INSERT INTO @Subs(empid, lvl)
  250. SELECT C.empid, @lvl
  251. FROM @Subs AS P -- P = Parent
  252. JOIN dbo.Employees AS C -- C = Child
  253. ON P.lvl = @lvl - 1 -- Filter parents from previous level 筛选上一级的父节点
  254. AND C.mgrid = P.empid;
  255. END
  256.  
  257. RETURN;
  258. END
  259. GO
  260.  
  261. -- Node ids of descendants of a given node返回员工ID3的下属
  262. SELECT empid, lvl FROM dbo.Subordinates1(3) AS S;
  263.  
  264. -- Descendants of a given node
  265. SELECT E.empid, E.empname, S.lvl
  266. FROM dbo.Subordinates1(3) AS S
  267. JOIN dbo.Employees AS E
  268. ON E.empid = S.empid;
  269.  
  270. -- Leaf nodes underneath a given node
  271. SELECT empid
  272. FROM dbo.Subordinates1(3) AS P
  273. WHERE NOT EXISTS
  274. (SELECT * FROM dbo.Employees AS C
  275. WHERE C.mgrid = P.empid);
  276.  
  277. -- Subtree of a Given Root, CTE Solution 锚点成员,返回
  278. DECLARE @root AS INT = 3;
  279. WITH Subs
  280. AS
  281. (
  282. -- Anchor member returns root node
  283. SELECT empid, empname, 0 AS lvl
  284. FROM dbo.Employees
  285. WHERE empid = @root
  286. UNION ALL
  287. -- Recursive member returns next level of children
  288. SELECT C.empid, C.empname, P.lvl + 1
  289. FROM Subs AS P
  290. JOIN dbo.Employees AS C
  291. ON C.mgrid = P.empid
  292. )
  293. SELECT * FROM Subs;
  294.  
  295. -- Creation Script for Function PartsExplosion
  296.  
  297. ---------------------------------------------------------------------
  298. -- Function: PartsExplosion, Parts Explosion 零件分解 另:产品零件组合
  299. --
  300. -- Input : @root INT: Root part id
  301. --
  302. -- Output : @PartsExplosion Table:
  303. -- id and level of contained parts of input part
  304. -- in all levels
  305. --
  306. -- Process : * Insert into @PartsExplosion row of input root part
  307. -- * In a loop, while previous insert loaded more than 0 rows
  308. -- insert into @PartsExplosion next level of parts
  309. ---------------------------------------------------------------------
  310. USE tempdb;
  311. GO
  312. IF OBJECT_ID('dbo.PartsExplosion') IS NOT NULL
  313. DROP FUNCTION dbo.PartsExplosion;
  314. GO
  315. CREATE FUNCTION dbo.PartsExplosion(@root AS INT)
  316. RETURNS @PartsExplosion Table
  317. (
  318. partid INT NOT NULL,
  319. qty DECIMAL(8, 2) NOT NULL,
  320. unit VARCHAR(3) NOT NULL,
  321. lvl INT NOT NULL,
  322. n INT NOT NULL IDENTITY, -- surrogate key 代理键
  323. UNIQUE CLUSTERED(lvl, n) -- Index will be used to filter lvl 用索引号LVL进行筛选
  324. )
  325. AS
  326. BEGIN
  327. DECLARE @lvl AS INT = 0; -- Initialize level counter with 0
  328.  
  329. -- Insert root node to @PartsExplosion
  330. INSERT INTO @PartsExplosion(partid, qty, unit, lvl)
  331. SELECT partid, qty, unit, @lvl
  332. FROM dbo.BOM
  333. WHERE partid = @root;
  334.  
  335. WHILE @@rowcount > 0 -- while previous level had rows
  336. BEGIN
  337. SET @lvl = @lvl + 1; -- Increment level counter
  338.  
  339. -- Insert next level of subordinates to @PartsExplosion
  340. INSERT INTO @PartsExplosion(partid, qty, unit, lvl)
  341. SELECT C.partid, P.qty * C.qty, C.unit, @lvl
  342. FROM @PartsExplosion AS P -- P = Parent
  343. JOIN dbo.BOM AS C -- C = Child
  344. ON P.lvl = @lvl - 1 -- Filter parents from previous level
  345. AND C.assemblyid = P.partid;
  346. END
  347.  
  348. RETURN;
  349. END
  350. GO
  351.  
  352. -- Parts Explosion UDF CTE 返回White Tea 的零部件
  353. SELECT P.partid, P.partname, PE.qty, PE.unit, PE.lvl
  354. FROM dbo.PartsExplosion(2) AS PE
  355. JOIN dbo.Parts AS P
  356. ON P.partid = PE.partid;
  357.  
  358. -- CTE Solution for Parts Explosion
  359. DECLARE @root AS INT = 2;
  360.  
  361. WITH PartsExplosion
  362. AS
  363. (
  364. -- Anchor member returns root part
  365. SELECT partid, qty, unit, 0 AS lvl
  366. FROM dbo.BOM
  367. WHERE partid = @root
  368.  
  369. UNION ALL
  370.  
  371. -- Recursive member returns next level of parts
  372. SELECT C.partid, CAST(P.qty * C.qty AS DECIMAL(8, 2)),
  373. C.unit, P.lvl + 1
  374. FROM PartsExplosion AS P
  375. JOIN dbo.BOM AS C
  376. ON C.assemblyid = P.partid
  377. )
  378. SELECT P.partid, P.partname, PE.qty, PE.unit, PE.lvl
  379. FROM PartsExplosion AS PE
  380. JOIN dbo.Parts AS P
  381. ON P.partid = PE.partid;
  382. GO
  383.  
  384. -- Parts Explosion, Aggregating Parts
  385. SELECT P.partid, P.partname, PES.qty, PES.unit
  386. FROM (SELECT partid, unit, SUM(qty) AS qty
  387. FROM dbo.PartsExplosion(2) AS PE
  388. GROUP BY partid, unit) AS PES
  389. JOIN dbo.Parts AS P
  390. ON P.partid = PES.partid;
  391. GO
  392.  
  393. SELECT P.partid, P.partname, PES.qty, PES.unit
  394. FROM (SELECT partid, unit, SUM(qty) AS qty
  395. FROM dbo.PartsExplosion(5) AS PE
  396. GROUP BY partid, unit) AS PES
  397. JOIN dbo.Parts AS P
  398. ON P.partid = PES.partid;
  399. GO
  400.  
  401. -- Creation Script for Function Subordinates2
  402.  
  403. ---------------------------------------------------------------------
  404. -- Function: Subordinates2,
  405. -- Descendants with optional level limit
  406. --
  407. -- Input : @root INT: Manager id
  408. -- @maxlevels INT: Max number of levels to return
  409. --
  410. -- Output : @Subs TABLE: id and level of subordinates of
  411. -- input manager in all levels <= @maxlevels
  412. --
  413. -- Process : * Insert into @Subs row of input manager
  414. -- * In a loop, while previous insert loaded more than 0 rows
  415. -- and previous level is smaller than @maxlevels
  416. -- insert into @Subs next level of subordinates
  417. ---------------------------------------------------------------------
  418. USE tempdb;
  419. GO
  420. IF OBJECT_ID('dbo.Subordinates2') IS NOT NULL
  421. DROP FUNCTION dbo.Subordinates2;
  422. GO
  423. CREATE FUNCTION dbo.Subordinates2
  424. (@root AS INT, @maxlevels AS INT = NULL) RETURNS @Subs TABLE
  425. (
  426. empid INT NOT NULL PRIMARY KEY NONCLUSTERED,
  427. lvl INT NOT NULL,
  428. UNIQUE CLUSTERED(lvl, empid) -- Index will be used to filter level
  429. )
  430. AS
  431. BEGIN
  432. DECLARE @lvl AS INT = 0; -- Initialize level counter with 0
  433. -- If input @maxlevels is NULL, set it to maximum integer
  434. -- to virtually have no limit on levels
  435. SET @maxlevels = COALESCE(@maxlevels, 2147483647);
  436.  
  437. -- Insert root node to @Subs
  438. INSERT INTO @Subs(empid, lvl)
  439. SELECT empid, @lvl FROM dbo.Employees WHERE empid = @root;
  440.  
  441. WHILE @@rowcount > 0 -- while previous level had rows
  442. AND @lvl < @maxlevels -- and previous level < @maxlevels
  443. BEGIN
  444. SET @lvl = @lvl + 1; -- Increment level counter
  445.  
  446. -- Insert next level of subordinates to @Subs
  447. INSERT INTO @Subs(empid, lvl)
  448. SELECT C.empid, @lvl
  449. FROM @Subs AS P -- P = Parent
  450. JOIN dbo.Employees AS C -- C = Child
  451. ON P.lvl = @lvl - 1 -- Filter parents from previous level
  452. AND C.mgrid = P.empid;
  453. END
  454.  
  455. RETURN;
  456. END
  457. GO
  458.  
  459. -- Descendants of a given node, no limit on levels
  460. SELECT empid, lvl
  461. FROM dbo.Subordinates2(3, NULL) AS S;
  462.  
  463. -- Descendants of a given node, limit 2 levels
  464. SELECT empid, lvl
  465. FROM dbo.Subordinates2(3, 2) AS S;
  466.  
  467. -- Descendants that are 2 levels underneath a given node 返回员工3以下第二级下属
  468. SELECT empid
  469. FROM dbo.Subordinates2(3, 2) AS S
  470. WHERE lvl = 2;
  471. GO
  472.  
  473. -- Subtree with level limit, CTE Solution, with MAXRECURSION
  474.  
  475. DECLARE @root AS INT = 3;
  476.  
  477. WITH Subs
  478. AS
  479. (
  480. SELECT empid, empname, 0 AS lvl
  481. FROM dbo.Employees
  482. WHERE empid = @root
  483.  
  484. UNION ALL
  485.  
  486. SELECT C.empid, C.empname, P.lvl + 1
  487. FROM Subs AS P
  488. JOIN dbo.Employees AS C
  489. ON C.mgrid = P.empid
  490. )
  491. SELECT * FROM Subs
  492. OPTION (MAXRECURSION 2);
  493. GO
  494.  
  495. -- Subtree with level limit, CTE Solution, with level column
  496. DECLARE @root AS INT = 3, @maxlevels AS INT = 2;
  497.  
  498. WITH Subs
  499. AS
  500. (
  501. SELECT empid, empname, 0 AS lvl
  502. FROM dbo.Employees
  503. WHERE empid = @root
  504.  
  505. UNION ALL
  506.  
  507. SELECT C.empid, C.empname, P.lvl + 1
  508. FROM Subs AS P
  509. JOIN dbo.Employees AS C
  510. ON C.mgrid = P.empid
  511. )
  512. SELECT * FROM Subs
  513. WHERE lvl <= @maxlevels;
  514.  
  515. ---------------------------------------------------------------------
  516. -- Sub-Path
  517. ---------------------------------------------------------------------
  518.  
  519. -- Creation Script for Function Managers
  520.  
  521. ---------------------------------------------------------------------
  522. -- Function: Managers, Ancestors with optional level limit 具有可选级数限制的祖先(父)
  523. --
  524. -- Input : @empid INT : Employee id
  525. -- @maxlevels : Max number of levels to return
  526. --
  527. -- Output : @Mgrs Table: id and level of managers of
  528. -- input employee in all levels <= @maxlevels
  529. --
  530. -- Process : * In a loop, while current manager is not null
  531. -- and previous level is smaller than @maxlevels
  532. -- insert into @Mgrs current manager,
  533. -- and get next level manager
  534. ---------------------------------------------------------------------
  535. USE tempdb;
  536. GO
  537. IF OBJECT_ID('dbo.Managers') IS NOT NULL
  538. DROP FUNCTION dbo.Managers;
  539. GO
  540. CREATE FUNCTION dbo.Managers
  541. (@empid AS INT, @maxlevels AS INT = NULL) RETURNS @Mgrs TABLE
  542. (
  543. empid INT NOT NULL PRIMARY KEY,
  544. lvl INT NOT NULL
  545. )
  546. AS
  547. BEGIN
  548. IF NOT EXISTS(SELECT * FROM dbo.Employees WHERE empid = @empid)
  549. RETURN;
  550.  
  551. DECLARE @lvl AS INT = 0; -- Initialize level counter with 0
  552. -- If input @maxlevels is NULL, set it to maximum integer
  553. -- to virtually have no limit on levels
  554. SET @maxlevels = COALESCE(@maxlevels, 2147483647);
  555.  
  556. WHILE @empid IS NOT NULL -- while current employee has a manager
  557. AND @lvl <= @maxlevels -- and previous level <= @maxlevels
  558. BEGIN
  559. -- Insert current manager to @Mgrs
  560. INSERT INTO @Mgrs(empid, lvl) VALUES(@empid, @lvl);
  561. SET @lvl = @lvl + 1; -- Increment level counter
  562. -- Get next level manager
  563. SET @empid = (SELECT mgrid FROM dbo.Employees WHERE empid = @empid);
  564. END
  565.  
  566. RETURN;
  567. END
  568. GO
  569.  
  570. -- Ancestors of a given node, no limit on levels 返回员工8的所有级别的经理
  571. SELECT empid, lvl
  572. FROM dbo.Managers(8, NULL) AS M;
  573.  
  574. -- Ancestors of a Given Node, CTE Solution
  575.  
  576. -- Ancestors of a given node, CTE Solution
  577. DECLARE @empid AS INT = 8;
  578.  
  579. WITH Mgrs
  580. AS
  581. (
  582. SELECT empid, mgrid, empname, 0 AS lvl
  583. FROM dbo.Employees
  584. WHERE empid = @empid
  585.  
  586. UNION ALL
  587.  
  588. SELECT P.empid, P.mgrid, P.empname, C.lvl + 1
  589. FROM Mgrs AS C
  590. JOIN dbo.Employees AS P
  591. ON C.mgrid = P.empid
  592. )
  593. SELECT * FROM Mgrs;
  594.  
  595. -- Ancestors of a given node, limit 2 levels
  596. SELECT empid, lvl
  597. FROM dbo.Managers(8, 2) AS M;
  598. GO
  599.  
  600. -- Ancestors with Level Limit, CTE Solution
  601. DECLARE @empid AS INT = 8, @maxlevels AS INT = 2;
  602.  
  603. WITH Mgrs
  604. AS
  605. (
  606. SELECT empid, mgrid, empname, 0 AS lvl
  607. FROM dbo.Employees
  608. WHERE empid = @empid
  609.  
  610. UNION ALL
  611.  
  612. SELECT P.empid, P.mgrid, P.empname, C.lvl + 1
  613. FROM Mgrs AS C
  614. JOIN dbo.Employees AS P
  615. ON C.mgrid = P.empid
  616. )
  617. SELECT * FROM Mgrs
  618. WHERE lvl <= @maxlevels;
  619.  
  620. -- Ancestor that is 2 levels above a given node
  621. SELECT empid
  622. FROM dbo.Managers(8, 2) AS M
  623. WHERE lvl = 2;
  624. GO
  625.  
  626. ---------------------------------------------------------------------
  627. -- Subtree/Subgraph with Path Enumeration 带有路径枚举的子图/子树
  628. ---------------------------------------------------------------------
  629.  
  630. -- Creation Script for Function Subordinates3
  631.  
  632. ---------------------------------------------------------------------
  633. -- Function: Subordinates3,
  634. -- Descendants with optional level limit,具有可选级数的下属,以及路径枚举
  635. -- and path enumeration
  636. --
  637. -- Input : @root INT: Manager id
  638. -- @maxlevels INT: Max number of levels to return
  639. --
  640. -- Output : @Subs TABLE: id, level and materialized ancestors path
  641. -- of subordinates of input manager
  642. -- in all levels <= @maxlevels
  643. --
  644. -- Process : * Insert into @Subs row of input manager
  645. -- * In a loop, while previous insert loaded more than 0 rows
  646. -- and previous level is smaller than @maxlevels:
  647. -- - insert into @Subs next level of subordinates
  648. -- - calculate a materialized ancestors path for each
  649. -- by concatenating current node id to parent's path
  650. ---------------------------------------------------------------------
  651. USE tempdb;
  652. GO
  653. IF OBJECT_ID('dbo.Subordinates3') IS NOT NULL
  654. DROP FUNCTION dbo.Subordinates3;
  655. GO
  656. CREATE FUNCTION dbo.Subordinates3
  657. (@root AS INT, @maxlevels AS INT = NULL) RETURNS @Subs TABLE
  658. (
  659. empid INT NOT NULL PRIMARY KEY NONCLUSTERED,
  660. lvl INT NOT NULL,
  661. path VARCHAR(900) NOT NULL
  662. UNIQUE CLUSTERED(lvl, empid) -- Index will be used to filter level
  663. )
  664. AS
  665. BEGIN
  666. DECLARE @lvl AS INT = 0; -- Initialize level counter with 0
  667. -- If input @maxlevels is NULL, set it to maximum integer
  668. -- to virtually have no limit on levels
  669. SET @maxlevels = COALESCE(@maxlevels, 2147483647);
  670.  
  671. -- Insert root node to @Subs
  672. INSERT INTO @Subs(empid, lvl, path)
  673. SELECT empid, @lvl, '.' + CAST(empid AS VARCHAR(10)) + '.'
  674. FROM dbo.Employees WHERE empid = @root;
  675.  
  676. WHILE @@rowcount > 0 -- while previous level had rows
  677. AND @lvl < @maxlevels -- and previous level < @maxlevels
  678. BEGIN
  679. SET @lvl = @lvl + 1; -- Increment level counter
  680.  
  681. -- Insert next level of subordinates to @Subs
  682. INSERT INTO @Subs(empid, lvl, path)
  683. SELECT C.empid, @lvl,
  684. P.path + CAST(C.empid AS VARCHAR(10)) + '.'
  685. FROM @Subs AS P -- P = Parent
  686. JOIN dbo.Employees AS C -- C = Child
  687. ON P.lvl = @lvl - 1 -- Filter parents from previous level
  688. AND C.mgrid = P.empid;
  689. END
  690.  
  691. RETURN;
  692. END
  693. GO
  694.  
  695. -- Return descendants of a given node, along with a materialized path
  696. SELECT empid, lvl, path
  697. FROM dbo.Subordinates3(1, NULL) AS S;
  698.  
  699. -- Return descendants of a given node, sorted and indented
  700. SELECT E.empid, REPLICATE(' | ', lvl) + empname AS empname
  701. FROM dbo.Subordinates3(1, NULL) AS S
  702. JOIN dbo.Employees AS E
  703. ON E.empid = S.empid
  704. ORDER BY path;
  705.  
  706. -- Subtree with Path Enumeration, CTE Solution
  707.  
  708. -- Descendants of a given node, with Materialized Path, CTE Solution
  709. DECLARE @root AS INT = 1;
  710.  
  711. WITH Subs
  712. AS
  713. (
  714. SELECT empid, empname, 0 AS lvl,
  715. -- Path of root = '.' + empid + '.'
  716. CAST('.' + CAST(empid AS VARCHAR(10)) + '.'
  717. AS VARCHAR(MAX)) AS path
  718. FROM dbo.Employees
  719. WHERE empid = @root
  720.  
  721. UNION ALL
  722.  
  723. SELECT C.empid, C.empname, P.lvl + 1,
  724. -- Path of child = parent's path + child empid + '.'
  725. CAST(P.path + CAST(C.empid AS VARCHAR(10)) + '.'
  726. AS VARCHAR(MAX))
  727. FROM Subs AS P
  728. JOIN dbo.Employees AS C
  729. ON C.mgrid = P.empid
  730. )
  731. SELECT empid, REPLICATE(' | ', lvl) + empname AS empname
  732. FROM Subs
  733. ORDER BY path;
  734.  
  735. ---------------------------------------------------------------------
  736. -- Sorting
  737. ---------------------------------------------------------------------
  738.  
  739. -- Sorting Hierarchy by empname, CTE Solution
  740. DECLARE @root AS INT = 1;
  741.  
  742. WITH Subs
  743. AS
  744. (
  745. SELECT empid, empname, 0 AS lvl,
  746. -- Path of root is 1 (binary)
  747. CAST(1 AS VARBINARY(MAX)) AS sort_path
  748. FROM dbo.Employees
  749. WHERE empid = @root
  750.  
  751. UNION ALL
  752.  
  753. SELECT C.empid, C.empname, P.lvl + 1,
  754. -- Path of child = parent's path + child row number (binary)
  755. P.sort_path + CAST(
  756. ROW_NUMBER() OVER(PARTITION BY C.mgrid
  757. ORDER BY C.empname) -- sort col(s)
  758. AS BINARY(4))
  759. FROM Subs AS P
  760. JOIN dbo.Employees AS C
  761. ON C.mgrid = P.empid
  762. )
  763. SELECT empid, ROW_NUMBER() OVER(ORDER BY sort_path) AS sortval,
  764. REPLICATE(' | ', lvl) + empname AS empname
  765. FROM Subs
  766. ORDER BY sortval;
  767. GO
  768.  
  769. -- Sorting Hierarchy by salary, CTE Solution
  770. DECLARE @root AS INT = 1;
  771.  
  772. WITH Subs
  773. AS
  774. (
  775. SELECT empid, empname, salary, 0 AS lvl,
  776. -- Path of root = 1 (binary)
  777. CAST(1 AS VARBINARY(MAX)) AS sort_path
  778. FROM dbo.Employees
  779. WHERE empid = @root
  780.  
  781. UNION ALL
  782.  
  783. SELECT C.empid, C.empname, C.salary, P.lvl + 1,
  784. -- Path of child = parent's path + child row number (binary)
  785. P.sort_path + CAST(
  786. ROW_NUMBER() OVER(PARTITION BY C.mgrid
  787. ORDER BY C.salary) -- sort col(s)
  788. AS BINARY(4))
  789. FROM Subs AS P
  790. JOIN dbo.Employees AS C
  791. ON C.mgrid = P.empid
  792. )
  793. SELECT empid, salary, ROW_NUMBER() OVER(ORDER BY sort_path) AS sortval,
  794. REPLICATE(' | ', lvl) + empname AS empname
  795. FROM Subs
  796. ORDER BY sortval;
  797.  
  798. ---------------------------------------------------------------------
  799. -- Cycles
  800. ---------------------------------------------------------------------
  801.  
  802. -- Create a cyclic path
  803. UPDATE dbo.Employees SET mgrid = 14 WHERE empid = 1;
  804. GO
  805.  
  806. -- Detecting Cycles, CTE Solution
  807. DECLARE @root AS INT = 1;
  808.  
  809. WITH Subs
  810. AS
  811. (
  812. SELECT empid, empname, 0 AS lvl,
  813. CAST('.' + CAST(empid AS VARCHAR(10)) + '.'
  814. AS VARCHAR(MAX)) AS path,
  815. -- Obviously root has no cycle
  816. 0 AS cycle
  817. FROM dbo.Employees
  818. WHERE empid = @root
  819.  
  820. UNION ALL
  821.  
  822. SELECT C.empid, C.empname, P.lvl + 1,
  823. CAST(P.path + CAST(C.empid AS VARCHAR(10)) + '.'
  824. AS VARCHAR(MAX)),
  825. -- Cycle detected if parent's path contains child's id
  826. CASE WHEN P.path LIKE '%.' + CAST(C.empid AS VARCHAR(10)) + '.%'
  827. THEN 1 ELSE 0 END
  828. FROM Subs AS P
  829. JOIN dbo.Employees AS C
  830. ON C.mgrid = P.empid
  831. )
  832. SELECT empid, empname, cycle, path
  833. FROM Subs;
  834. GO
  835.  
  836. -- Not Pursuing Cycles, CTE Solution
  837. DECLARE @root AS INT = 1;
  838.  
  839. WITH Subs
  840. AS
  841. (
  842. SELECT empid, empname, 0 AS lvl,
  843. CAST('.' + CAST(empid AS VARCHAR(10)) + '.'
  844. AS VARCHAR(MAX)) AS path,
  845. -- Obviously root has no cycle
  846. 0 AS cycle
  847. FROM dbo.Employees
  848. WHERE empid = @root
  849.  
  850. UNION ALL
  851.  
  852. SELECT C.empid, C.empname, P.lvl + 1,
  853. CAST(P.path + CAST(C.empid AS VARCHAR(10)) + '.'
  854. AS VARCHAR(MAX)),
  855. -- Cycle detected if parent's path contains child's id
  856. CASE WHEN P.path LIKE '%.' + CAST(C.empid AS VARCHAR(10)) + '.%'
  857. THEN 1 ELSE 0 END
  858. FROM Subs AS P
  859. JOIN dbo.Employees AS C
  860. ON C.mgrid = P.empid
  861. AND P.cycle = 0 -- do not pursue branch for parent with cycle
  862. )
  863. SELECT empid, empname, cycle, path
  864. FROM Subs;
  865. GO
  866.  
  867. -- Isolating Cyclic Paths, CTE Solution
  868. DECLARE @root AS INT = 1;
  869.  
  870. WITH Subs
  871. AS
  872. (
  873. SELECT empid, empname, 0 AS lvl,
  874. CAST('.' + CAST(empid AS VARCHAR(10)) + '.'
  875. AS VARCHAR(MAX)) AS path,
  876. -- Obviously root has no cycle
  877. 0 AS cycle
  878. FROM dbo.Employees
  879. WHERE empid = @root
  880.  
  881. UNION ALL
  882.  
  883. SELECT C.empid, C.empname, P.lvl + 1,
  884. CAST(P.path + CAST(C.empid AS VARCHAR(10)) + '.'
  885. AS VARCHAR(MAX)),
  886. -- Cycle detected if parent's path contains child's id
  887. CASE WHEN P.path LIKE '%.' + CAST(C.empid AS VARCHAR(10)) + '.%'
  888. THEN 1 ELSE 0 END
  889. FROM Subs AS P
  890. JOIN dbo.Employees AS C
  891. ON C.mgrid = P.empid
  892. AND P.cycle = 0
  893. )
  894. SELECT path FROM Subs WHERE cycle = 1;
  895.  
  896. -- Fix cyclic path
  897. UPDATE dbo.Employees SET mgrid = NULL WHERE empid = 1;
  898.  
  899. ---------------------------------------------------------------------
  900. -- Materialized Path
  901. ---------------------------------------------------------------------
  902.  
  903. ---------------------------------------------------------------------
  904. -- Custom Materialized Path
  905. ---------------------------------------------------------------------
  906.  
  907. ---------------------------------------------------------------------
  908. -- Maintaining Data
  909. ---------------------------------------------------------------------
  910.  
  911. -- DDL for Employees with Path
  912. SET NOCOUNT ON;
  913. USE tempdb;
  914. GO
  915. IF OBJECT_ID('dbo.Employees') IS NOT NULL
  916. DROP TABLE dbo.Employees;
  917. GO
  918. CREATE TABLE dbo.Employees
  919. (
  920. empid INT NOT NULL PRIMARY KEY NONCLUSTERED,
  921. mgrid INT NULL REFERENCES dbo.Employees,
  922. empname VARCHAR(25) NOT NULL,
  923. salary MONEY NOT NULL,
  924. lvl INT NOT NULL,
  925. path VARCHAR(900) NOT NULL UNIQUE CLUSTERED
  926. );
  927. CREATE UNIQUE INDEX idx_unc_mgrid_empid ON dbo.Employees(mgrid, empid);
  928. GO
  929.  
  930. ---------------------------------------------------------------------
  931. -- Adding Employees who Manage No One (Leaves) 具体化路径
  932. ---------------------------------------------------------------------
  933.  
  934. -- Creation Script for Procedure AddEmp
  935.  
  936. ---------------------------------------------------------------------
  937. -- Stored Procedure: AddEmp,
  938. -- Inserts new employee who manages no one into the table
  939. ---------------------------------------------------------------------
  940. USE tempdb;
  941. GO
  942. IF OBJECT_ID('dbo.AddEmp') IS NOT NULL
  943. DROP PROC dbo.AddEmp;
  944. GO
  945. CREATE PROC dbo.AddEmp
  946. @empid INT,
  947. @mgrid INT,
  948. @empname VARCHAR(25),
  949. @salary MONEY
  950. AS
  951.  
  952. SET NOCOUNT ON;
  953.  
  954. -- Handle case where the new employee has no manager (root)
  955. IF @mgrid IS NULL
  956. INSERT INTO dbo.Employees(empid, mgrid, empname, salary, lvl, path)
  957. VALUES(@empid, @mgrid, @empname, @salary,
  958. 0, '.' + CAST(@empid AS VARCHAR(10)) + '.');
  959. -- Handle subordinate case (non-root)
  960. ELSE
  961. INSERT INTO dbo.Employees(empid, mgrid, empname, salary, lvl, path)
  962. SELECT @empid, @mgrid, @empname, @salary,
  963. lvl + 1, path + CAST(@empid AS VARCHAR(10)) + '.'
  964. FROM dbo.Employees
  965. WHERE empid = @mgrid;
  966. GO
  967.  
  968. -- Sample Data for Employees with Path
  969. EXEC dbo.AddEmp
  970. @empid = 1, @mgrid = NULL, @empname = 'David', @salary = $10000.00;
  971. EXEC dbo.AddEmp
  972. @empid = 2, @mgrid = 1, @empname = 'Eitan', @salary = $7000.00;
  973. EXEC dbo.AddEmp
  974. @empid = 3, @mgrid = 1, @empname = 'Ina', @salary = $7500.00;
  975. EXEC dbo.AddEmp
  976. @empid = 4, @mgrid = 2, @empname = 'Seraph', @salary = $5000.00;
  977. EXEC dbo.AddEmp
  978. @empid = 5, @mgrid = 2, @empname = 'Jiru', @salary = $5500.00;
  979. EXEC dbo.AddEmp
  980. @empid = 6, @mgrid = 2, @empname = 'Steve', @salary = $4500.00;
  981. EXEC dbo.AddEmp
  982. @empid = 7, @mgrid = 3, @empname = 'Aaron', @salary = $5000.00;
  983. EXEC dbo.AddEmp
  984. @empid = 8, @mgrid = 5, @empname = 'Lilach', @salary = $3500.00;
  985. EXEC dbo.AddEmp
  986. @empid = 9, @mgrid = 7, @empname = 'Rita', @salary = $3000.00;
  987. EXEC dbo.AddEmp
  988. @empid = 10, @mgrid = 5, @empname = 'Sean', @salary = $3000.00;
  989. EXEC dbo.AddEmp
  990. @empid = 11, @mgrid = 7, @empname = 'Gabriel', @salary = $3000.00;
  991. EXEC dbo.AddEmp
  992. @empid = 12, @mgrid = 9, @empname = 'Emilia', @salary = $2000.00;
  993. EXEC dbo.AddEmp
  994. @empid = 13, @mgrid = 9, @empname = 'Michael', @salary = $2000.00;
  995. EXEC dbo.AddEmp
  996. @empid = 14, @mgrid = 9, @empname = 'Didi', @salary = $1500.00;
  997. GO
  998.  
  999. -- Examine data after load
  1000. SELECT empid, mgrid, empname, salary, lvl, path
  1001. FROM dbo.Employees
  1002. ORDER BY path;
  1003.  
  1004. ---------------------------------------------------------------------
  1005. -- Moving a Subtree
  1006. ---------------------------------------------------------------------
  1007.  
  1008. -- Creation Script for Procedure MoveSubtree
  1009.  
  1010. ---------------------------------------------------------------------
  1011. -- Stored Procedure: MoveSubtree,
  1012. -- Moves a whole subtree of a given root to a new location
  1013. -- under a given manager
  1014. ---------------------------------------------------------------------
  1015. USE tempdb;
  1016. GO
  1017. IF OBJECT_ID('dbo.MoveSubtree') IS NOT NULL
  1018. DROP PROC dbo.MoveSubtree;
  1019. GO
  1020. CREATE PROC dbo.MoveSubtree
  1021. @root INT,
  1022. @mgrid INT
  1023. AS
  1024.  
  1025. SET NOCOUNT ON;
  1026.  
  1027. BEGIN TRAN;
  1028. -- Update level and path of all employees in the subtree (E)
  1029. -- Set level =
  1030. -- current level + new manager's level - old manager's level
  1031. -- Set path =
  1032. -- in current path remove old manager's path
  1033. -- and substitute with new manager's path
  1034. UPDATE E
  1035. SET lvl = E.lvl + NM.lvl - OM.lvl,
  1036. path = STUFF(E.path, 1, LEN(OM.path), NM.path)
  1037. FROM dbo.Employees AS E -- E = Employees (subtree)
  1038. JOIN dbo.Employees AS R -- R = Root (one row)
  1039. ON R.empid = @root
  1040. AND E.path LIKE R.path + '%'
  1041. JOIN dbo.Employees AS OM -- OM = Old Manager (one row)
  1042. ON OM.empid = R.mgrid
  1043. JOIN dbo.Employees AS NM -- NM = New Manager (one row)
  1044. ON NM.empid = @mgrid;
  1045.  
  1046. -- Update root's new manager
  1047. UPDATE dbo.Employees SET mgrid = @mgrid WHERE empid = @root;
  1048. COMMIT TRAN;
  1049. GO
  1050.  
  1051. -- Before moving subtree
  1052. SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path
  1053. FROM dbo.Employees
  1054. ORDER BY path;
  1055.  
  1056. -- Move Subtree 移动子树
  1057. BEGIN TRAN;
  1058.  
  1059. EXEC dbo.MoveSubtree
  1060. @root = 7,
  1061. @mgrid = 10;
  1062.  
  1063. -- After moving subtree
  1064. SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path
  1065. FROM dbo.Employees
  1066. ORDER BY path;
  1067.  
  1068. ROLLBACK TRAN; -- rollback used in order not to apply the change
  1069.  
  1070. ---------------------------------------------------------------------
  1071. -- Removing a Subtree
  1072. ---------------------------------------------------------------------
  1073.  
  1074. -- Before deleteting subtree
  1075. SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path
  1076. FROM dbo.Employees
  1077. ORDER BY path;
  1078.  
  1079. -- Delete Subtree 删除子树
  1080. BEGIN TRAN;
  1081.  
  1082. DELETE FROM dbo.Employees
  1083. WHERE path LIKE
  1084. (SELECT M.path + '%'
  1085. FROM dbo.Employees as M
  1086. WHERE M.empid = 7);
  1087.  
  1088. -- After deleting subtree
  1089. SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, lvl, path
  1090. FROM dbo.Employees
  1091. ORDER BY path;
  1092.  
  1093. ROLLBACK TRAN; -- rollback used in order not to apply the change
  1094.  
  1095. ---------------------------------------------------------------------
  1096. -- Querying Materialized Path 查询
  1097. ---------------------------------------------------------------------
  1098.  
  1099. -- Subtree of a given root
  1100. SELECT REPLICATE(' | ', E.lvl - M.lvl) + E.empname
  1101. FROM dbo.Employees AS E
  1102. JOIN dbo.Employees AS M
  1103. ON M.empid = 3 -- root
  1104. AND E.path LIKE M.path + '%'
  1105. ORDER BY E.path;
  1106.  
  1107. -- Subtree of a given root, excluding root
  1108. SELECT REPLICATE(' | ', E.lvl - M.lvl - 1) + E.empname
  1109. FROM dbo.Employees AS E
  1110. JOIN dbo.Employees AS M
  1111. ON M.empid = 3
  1112. AND E.path LIKE M.path + '_%'
  1113. ORDER BY E.path;
  1114.  
  1115. -- Leaf nodes under a given root
  1116. SELECT E.empid, E.empname
  1117. FROM dbo.Employees AS E
  1118. JOIN dbo.Employees AS M
  1119. ON M.empid = 3
  1120. AND E.path LIKE M.path + '%'
  1121. WHERE NOT EXISTS
  1122. (SELECT *
  1123. FROM dbo.Employees AS E2
  1124. WHERE E2.mgrid = E.empid);
  1125.  
  1126. -- Subtree of a given root, limit number of levels
  1127. SELECT REPLICATE(' | ', E.lvl - M.lvl) + E.empname
  1128. FROM dbo.Employees AS E
  1129. JOIN dbo.Employees AS M
  1130. ON M.empid = 3
  1131. AND E.path LIKE M.path + '%'
  1132. AND E.lvl - M.lvl <= 2
  1133. ORDER BY E.path;
  1134.  
  1135. -- Nodes that are n levels under a given root
  1136. SELECT E.empid, E.empname
  1137. FROM dbo.Employees AS E
  1138. JOIN dbo.Employees AS M
  1139. ON M.empid = 3
  1140. AND E.path LIKE M.path + '%'
  1141. AND E.lvl - M.lvl = 2;
  1142.  
  1143. -- Ancestors of a given node (requires a table scan)
  1144. SELECT REPLICATE(' | ', M.lvl) + M.empname
  1145. FROM dbo.Employees AS E
  1146. JOIN dbo.Employees AS M
  1147. ON E.empid = 14
  1148. AND E.path LIKE M.path + '%'
  1149. ORDER BY E.path;
  1150.  
  1151. -- Creating and Populating Auxiliary Table of Numbers
  1152. SET NOCOUNT ON;
  1153. USE tempdb;
  1154. GO
  1155. IF OBJECT_ID('dbo.Nums') IS NOT NULL
  1156. DROP TABLE dbo.Nums;
  1157. GO
  1158. CREATE TABLE dbo.Nums(n INT NOT NULL PRIMARY KEY);
  1159. DECLARE @max AS INT = 1000000, @rc AS INT = 1;
  1160.  
  1161. INSERT INTO Nums VALUES(1);
  1162. WHILE @rc * 2 <= @max
  1163. BEGIN
  1164. INSERT INTO dbo.Nums SELECT n + @rc FROM dbo.Nums;
  1165. SET @rc = @rc * 2;
  1166. END
  1167.  
  1168. INSERT INTO dbo.Nums
  1169. SELECT n + @rc FROM dbo.Nums WHERE n + @rc <= @max;
  1170. GO
  1171.  
  1172. -- Creation Script for Function SplitPath
  1173. USE tempdb;
  1174. GO
  1175. IF OBJECT_ID('dbo.SplitPath') IS NOT NULL
  1176. DROP FUNCTION dbo.SplitPath;
  1177. GO
  1178. CREATE FUNCTION dbo.SplitPath(@empid AS INT) RETURNS TABLE
  1179. AS
  1180. RETURN
  1181. SELECT
  1182. ROW_NUMBER() OVER(ORDER BY n) AS pos,
  1183. CAST(SUBSTRING(path, n + 1,
  1184. CHARINDEX('.', path, n + 1) - n - 1) AS INT) AS empid
  1185. FROM dbo.Employees
  1186. JOIN dbo.Nums
  1187. ON empid = @empid
  1188. AND n < LEN(path)
  1189. AND SUBSTRING(path, n, 1) = '.';
  1190. GO
  1191.  
  1192. -- Test SplitPath function
  1193. SELECT pos, empid FROM dbo.SplitPath(14);
  1194.  
  1195. -- Getting ancestors using SplitPath function
  1196. SELECT REPLICATE(' | ', lvl) + empname
  1197. FROM dbo.SplitPath(14) AS SP
  1198. JOIN dbo.Employees AS E
  1199. ON E.empid = SP.empid
  1200. ORDER BY path;
  1201.  
  1202. -- Presentation
  1203. SELECT REPLICATE(' | ', lvl) + empname
  1204. FROM dbo.Employees
  1205. ORDER BY path;
  1206.  
  1207. ---------------------------------------------------------------------
  1208. -- Materialized Path with the HIERARCHYID Datatype 使用HIERARCHYID数据类型的具体化路径
  1209. --HIERARCHYID类型提供了下列方法和属性:GetLevel,GetRoot,GetAncestor,GetDescendant,GetReparentedValue,IsDescendantOf,ToString,Parse,Read,Write
  1210. ---------------------------------------------------------------------
  1211.  
  1212. SET NOCOUNT ON;
  1213. USE tempdb;
  1214. GO
  1215. IF OBJECT_ID('dbo.Employees') IS NOT NULL
  1216. DROP TABLE dbo.Employees;
  1217. GO
  1218. CREATE TABLE dbo.Employees
  1219. (
  1220. empid INT NOT NULL,
  1221. hid HIERARCHYID NOT NULL,
  1222. lvl AS hid.GetLevel() PERSISTED,
  1223. empname VARCHAR(25) NOT NULL,
  1224. salary MONEY NOT NULL,
  1225. mgrid INT NULL
  1226. );
  1227.  
  1228. CREATE UNIQUE CLUSTERED INDEX idx_depth_first ON dbo.Employees(hid);
  1229. CREATE UNIQUE INDEX idx_breadth_first ON dbo.Employees(lvl, hid);
  1230. CREATE UNIQUE INDEX idx_empid ON dbo.Employees(empid);
  1231. GO
  1232.  
  1233. ---------------------------------------------------------------------
  1234. -- Maintaining Data
  1235. ---------------------------------------------------------------------
  1236.  
  1237. ---------------------------------------------------------------------
  1238. -- Adding Employees
  1239. ---------------------------------------------------------------------
  1240.  
  1241. ---------------------------------------------------------------------
  1242. -- Stored Procedure: AddEmp,
  1243. -- Inserts new employee who manages no one into the table
  1244. ---------------------------------------------------------------------
  1245. IF OBJECT_ID('dbo.AddEmp', 'P') IS NOT NULL
  1246. DROP PROC dbo.AddEmp;
  1247. GO
  1248. CREATE PROC dbo.AddEmp
  1249. @empid AS INT,
  1250. @mgrid AS INT,
  1251. @empname AS VARCHAR(25),
  1252. @salary AS MONEY
  1253. AS
  1254.  
  1255. DECLARE
  1256. @hid AS HIERARCHYID,
  1257. @mgr_hid AS HIERARCHYID,
  1258. @last_child_hid AS HIERARCHYID;
  1259.  
  1260. BEGIN TRAN
  1261.  
  1262. IF @mgrid IS NULL
  1263. SET @hid = hierarchyid::GetRoot();
  1264. ELSE
  1265. BEGIN
  1266. SET @mgr_hid = (SELECT hid FROM dbo.Employees WITH (UPDLOCK)
  1267. WHERE empid = @mgrid);
  1268. SET @last_child_hid =
  1269. (SELECT MAX(hid) FROM dbo.Employees
  1270. WHERE hid.GetAncestor(1) = @mgr_hid);
  1271. SET @hid = @mgr_hid.GetDescendant(@last_child_hid, NULL);
  1272. END
  1273.  
  1274. INSERT INTO dbo.Employees(empid, hid, empname, salary)
  1275. VALUES(@empid, @hid, @empname, @salary);
  1276.  
  1277. COMMIT TRAN
  1278. GO
  1279.  
  1280. EXEC dbo.AddEmp @empid = 1, @mgrid = NULL, @empname = 'David' , @salary = $10000.00;
  1281. EXEC dbo.AddEmp @empid = 2, @mgrid = 1, @empname = 'Eitan' , @salary = $7000.00;
  1282. EXEC dbo.AddEmp @empid = 3, @mgrid = 1, @empname = 'Ina' , @salary = $7500.00;
  1283. EXEC dbo.AddEmp @empid = 4, @mgrid = 2, @empname = 'Seraph' , @salary = $5000.00;
  1284. EXEC dbo.AddEmp @empid = 5, @mgrid = 2, @empname = 'Jiru' , @salary = $5500.00;
  1285. EXEC dbo.AddEmp @empid = 6, @mgrid = 2, @empname = 'Steve' , @salary = $4500.00;
  1286. EXEC dbo.AddEmp @empid = 7, @mgrid = 3, @empname = 'Aaron' , @salary = $5000.00;
  1287. EXEC dbo.AddEmp @empid = 8, @mgrid = 5, @empname = 'Lilach' , @salary = $3500.00;
  1288. EXEC dbo.AddEmp @empid = 9, @mgrid = 7, @empname = 'Rita' , @salary = $3000.00;
  1289. EXEC dbo.AddEmp @empid = 10, @mgrid = 5, @empname = 'Sean' , @salary = $3000.00;
  1290. EXEC dbo.AddEmp @empid = 11, @mgrid = 7, @empname = 'Gabriel', @salary = $3000.00;
  1291. EXEC dbo.AddEmp @empid = 12, @mgrid = 9, @empname = 'Emilia' , @salary = $2000.00;
  1292. EXEC dbo.AddEmp @empid = 13, @mgrid = 9, @empname = 'Michael', @salary = $2000.00;
  1293. EXEC dbo.AddEmp @empid = 14, @mgrid = 9, @empname = 'Didi' , @salary = $1500.00;
  1294. GO
  1295.  
  1296. SELECT hid, hid.ToString() AS path, lvl, empid, empname, salary
  1297. FROM dbo.Employees
  1298. ORDER BY hid;
  1299.  
  1300. SELECT *
  1301. FROM dbo.Employees
  1302. ---------------------------------------------------------------------
  1303. -- Moving a Subtree
  1304. ---------------------------------------------------------------------
  1305.  
  1306. SELECT
  1307. CAST('/1/1/2/3/2/' AS HIERARCHYID).GetReparentedValue('/1/1/', '/2/1/4/').ToString();
  1308.  
  1309. ---------------------------------------------------------------------
  1310. -- Stored Procedure: MoveSubtree, 移动子树
  1311. -- Moves a whole subtree of a given root to a new location
  1312. -- under a given manager
  1313. ---------------------------------------------------------------------
  1314. IF OBJECT_ID('dbo.MoveSubtree') IS NOT NULL
  1315. DROP PROC dbo.MoveSubtree;
  1316. GO
  1317. CREATE PROC dbo.MoveSubtree
  1318. @empid AS INT,
  1319. @new_mgrid AS INT
  1320. AS
  1321.  
  1322. DECLARE
  1323. @old_root AS HIERARCHYID,
  1324. @new_root AS HIERARCHYID,
  1325. @new_mgr_hid AS HIERARCHYID;
  1326.  
  1327. BEGIN TRAN
  1328.  
  1329. SET @new_mgr_hid = (SELECT hid FROM dbo.Employees WITH (UPDLOCK)
  1330. WHERE empid = @new_mgrid);
  1331. SET @old_root = (SELECT hid FROM dbo.Employees
  1332. WHERE empid = @empid);
  1333.  
  1334. -- First, get a new hid for the subtree root employee that moves
  1335. SET @new_root = @new_mgr_hid.GetDescendant
  1336. ((SELECT MAX(hid)
  1337. FROM dbo.Employees
  1338. WHERE hid.GetAncestor(1) = @new_mgr_hid),
  1339. NULL);
  1340.  
  1341. -- Next, reparent all descendants of employee that moves
  1342. UPDATE dbo.Employees
  1343. SET hid = hid.GetReparentedValue(@old_root, @new_root)
  1344. WHERE hid.IsDescendantOf(@old_root) = 1;
  1345.  
  1346. COMMIT TRAN
  1347. GO
  1348.  
  1349. SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, hid.ToString() AS path
  1350. FROM dbo.Employees
  1351. ORDER BY hid;
  1352.  
  1353. BEGIN TRAN
  1354.  
  1355. EXEC dbo.MoveSubtree
  1356. @empid = 5,
  1357. @new_mgrid = 9;
  1358.  
  1359. SELECT empid, REPLICATE(' | ', lvl) + empname AS empname, hid.ToString() AS path
  1360. FROM dbo.Employees
  1361. ORDER BY hid;
  1362.  
  1363. ROLLBACK TRAN
  1364. GO
  1365.  
  1366. ---------------------------------------------------------------------
  1367. -- Querying 查询
  1368. ---------------------------------------------------------------------
  1369.  
  1370. -- Subtree
  1371. SELECT E.empid, E.empname
  1372. FROM dbo.Employees AS M
  1373. JOIN dbo.Employees AS E
  1374. ON M.empid = 3
  1375. AND E.hid.IsDescendantOf(M.hid) = 1
  1376. WHERE E.lvl - M.lvl <= 3;
  1377.  
  1378. -- Path 路径
  1379. SELECT M.empid, M.empname
  1380. FROM dbo.Employees AS M
  1381. JOIN dbo.Employees AS E
  1382. ON E.empid = 14
  1383. AND E.hid.IsDescendantOf(M.hid) = 1;
  1384.  
  1385. -- Direct Subordinates 直接下属
  1386. SELECT E.empid, E.empname
  1387. FROM dbo.Employees AS M
  1388. JOIN dbo.Employees AS E
  1389. ON M.empid = 2
  1390. AND E.hid.GetAncestor(1) = M.hid;
  1391.  
  1392. -- Leaf nodes 叶子节点
  1393. SELECT empid, empname
  1394. FROM dbo.Employees AS M
  1395. WHERE NOT EXISTS
  1396. (SELECT * FROM dbo.Employees AS E
  1397. WHERE E.hid.GetAncestor(1) = M.hid);
  1398.  
  1399. -- Presentation/sorting
  1400. SELECT REPLICATE(' | ', lvl) + empname AS empname, hid.ToString() AS path
  1401. FROM dbo.Employees
  1402. ORDER BY hid;
  1403.  
  1404. ---------------------------------------------------------------------
  1405. -- Further Aspects of Working with HIERARCHYID
  1406. ---------------------------------------------------------------------
  1407.  
  1408. ---------------------------------------------------------------------
  1409. -- Normalizing HIERARCHYID Values
  1410. ---------------------------------------------------------------------
  1411.  
  1412. ---------------------------------------------------------------------
  1413. -- Stored Procedure: AddEmp,
  1414. -- Inserts new employee who manages no one into the table
  1415. ---------------------------------------------------------------------
  1416. IF OBJECT_ID('dbo.AddEmp', 'P') IS NOT NULL
  1417. DROP PROC dbo.AddEmp;
  1418. GO
  1419. CREATE PROC dbo.AddEmp
  1420. @empid AS INT,
  1421. @mgrid AS INT,
  1422. @leftempid AS INT,
  1423. @rightempid AS INT,
  1424. @empname AS VARCHAR(25),
  1425. @salary AS MONEY = 1000
  1426. AS
  1427.  
  1428. DECLARE @hid AS HIERARCHYID;
  1429.  
  1430. IF @mgrid IS NULL
  1431. SET @hid = hierarchyid::GetRoot();
  1432. ELSE
  1433. SET @hid = (SELECT hid FROM dbo.Employees WHERE empid = @mgrid).GetDescendant
  1434. ( (SELECT hid FROM dbo.Employees WHERE empid = @leftempid),
  1435. (SELECT hid FROM dbo.Employees WHERE empid = @rightempid) );
  1436.  
  1437. INSERT INTO dbo.Employees(empid, hid, empname, salary)
  1438. VALUES(@empid, @hid, @empname, @salary);
  1439. GO
  1440.  
  1441. TRUNCATE TABLE dbo.Employees;
  1442.  
  1443. EXEC dbo.AddEmp @empid = 1, @mgrid = NULL, @leftempid = NULL, @rightempid = NULL, @empname = 'A';
  1444. EXEC dbo.AddEmp @empid = 2, @mgrid = 1, @leftempid = NULL, @rightempid = NULL, @empname = 'B';
  1445. EXEC dbo.AddEmp @empid = 3, @mgrid = 1, @leftempid = 2, @rightempid = NULL, @empname = 'C';
  1446. EXEC dbo.AddEmp @empid = 4, @mgrid = 1, @leftempid = 2, @rightempid = 3, @empname = 'D';
  1447. EXEC dbo.AddEmp @empid = 5, @mgrid = 1, @leftempid = 4, @rightempid = 3, @empname = 'E';
  1448. EXEC dbo.AddEmp @empid = 6, @mgrid = 1, @leftempid = 4, @rightempid = 5, @empname = 'F';
  1449. EXEC dbo.AddEmp @empid = 7, @mgrid = 1, @leftempid = 6, @rightempid = 5, @empname = 'G';
  1450. EXEC dbo.AddEmp @empid = 8, @mgrid = 1, @leftempid = 6, @rightempid = 7, @empname = 'H';
  1451. EXEC dbo.AddEmp @empid = 9, @mgrid = 8, @leftempid = NULL, @rightempid = NULL, @empname = 'I';
  1452. EXEC dbo.AddEmp @empid = 10, @mgrid = 8, @leftempid = 9, @rightempid = NULL, @empname = 'J';
  1453. EXEC dbo.AddEmp @empid = 11, @mgrid = 8, @leftempid = 9, @rightempid = 10, @empname = 'K';
  1454. EXEC dbo.AddEmp @empid = 12, @mgrid = 8, @leftempid = 11, @rightempid = 10, @empname = 'J';
  1455. EXEC dbo.AddEmp @empid = 13, @mgrid = 8, @leftempid = 11, @rightempid = 12, @empname = 'L';
  1456. EXEC dbo.AddEmp @empid = 14, @mgrid = 8, @leftempid = 13, @rightempid = 12, @empname = 'M';
  1457. EXEC dbo.AddEmp @empid = 15, @mgrid = 8, @leftempid = 13, @rightempid = 14, @empname = 'N';
  1458. EXEC dbo.AddEmp @empid = 16, @mgrid = 8, @leftempid = 15, @rightempid = 14, @empname = 'O';
  1459. EXEC dbo.AddEmp @empid = 17, @mgrid = 8, @leftempid = 15, @rightempid = 16, @empname = 'P';
  1460. EXEC dbo.AddEmp @empid = 18, @mgrid = 8, @leftempid = 17, @rightempid = 16, @empname = 'Q';
  1461. EXEC dbo.AddEmp @empid = 19, @mgrid = 8, @leftempid = 17, @rightempid = 18, @empname = 'E';
  1462. EXEC dbo.AddEmp @empid = 20, @mgrid = 8, @leftempid = 19, @rightempid = 18, @empname = 'S';
  1463. EXEC dbo.AddEmp @empid = 21, @mgrid = 8, @leftempid = 19, @rightempid = 20, @empname = 'T';
  1464. GO
  1465.  
  1466. SELECT *
  1467. FROM dbo.Employees
  1468.  
  1469. -- Data before normalization
  1470. SELECT
  1471. empid,
  1472. REPLICATE(' | ', lvl) + empname AS emp,
  1473. hid,
  1474. hid.ToString() AS path
  1475. FROM dbo.Employees
  1476. ORDER BY hid;
  1477.  
  1478. -- Normalize
  1479. WITH EmpsRN AS
  1480. (
  1481. SELECT
  1482. empid,
  1483. hid,
  1484. ROW_NUMBER() OVER(PARTITION BY hid.GetAncestor(1) ORDER BY hid) AS rownum
  1485. FROM dbo.Employees
  1486. ),
  1487. EmpPaths AS
  1488. (
  1489. SELECT empid, hid, CAST('/' AS VARCHAR(900)) AS path
  1490. FROM dbo.Employees
  1491. WHERE hid = hierarchyid::GetRoot()
  1492.  
  1493. UNION ALL
  1494.  
  1495. SELECT C.empid, C.hid,
  1496. CAST(P.path + CAST(C.rownum AS VARCHAR(20)) + '/' AS VARCHAR(900))
  1497. FROM EmpPaths AS P
  1498. JOIN EmpsRN AS C
  1499. ON C.hid.GetAncestor(1) = P.hid
  1500. )
  1501. UPDATE E
  1502. SET hid = CAST(EP.path AS HIERARCHYID)
  1503. FROM dbo.Employees AS E
  1504. JOIN EmpPaths AS EP
  1505. ON E.empid = EP.empid;
  1506.  
  1507. -- Data after normalization
  1508. SELECT
  1509. empid,
  1510. REPLICATE(' | ', lvl) + empname AS emp,
  1511. hid,
  1512. hid.ToString() AS path
  1513. FROM dbo.Employees
  1514. ORDER BY hid;
  1515.  
  1516. ---------------------------------------------------------------------
  1517. -- Convert parent-child representation to new hierarchyid
  1518. ---------------------------------------------------------------------
  1519.  
  1520. SET NOCOUNT ON;
  1521. USE tempdb;
  1522. GO
  1523. IF OBJECT_ID('dbo.EmployeesOld') IS NOT NULL
  1524. DROP TABLE dbo.EmployeesOld;
  1525. GO
  1526. IF OBJECT_ID('dbo.EmployeesNew') IS NOT NULL
  1527. DROP TABLE dbo.EmployeesNew;
  1528. GO
  1529. CREATE TABLE dbo.EmployeesOld
  1530. (
  1531. empid INT PRIMARY KEY,
  1532. mgrid INT NULL REFERENCES dbo.EmployeesOld,
  1533. empname VARCHAR(25) NOT NULL,
  1534. salary MONEY NOT NULL
  1535. );
  1536. CREATE UNIQUE INDEX idx_unc_mgrid_empid ON dbo.EmployeesOld(mgrid, empid);
  1537.  
  1538. INSERT INTO dbo.EmployeesOld(empid, mgrid, empname, salary) VALUES
  1539. (1, NULL, 'David', $10000.00),
  1540. (2, 1, 'Eitan', $7000.00),
  1541. (3, 1, 'Ina', $7500.00),
  1542. (4, 2, 'Seraph', $5000.00),
  1543. (5, 2, 'Jiru', $5500.00),
  1544. (6, 2, 'Steve', $4500.00),
  1545. (7, 3, 'Aaron', $5000.00),
  1546. (8, 5, 'Lilach', $3500.00),
  1547. (9, 7, 'Rita', $3000.00),
  1548. (10, 5, 'Sean', $3000.00),
  1549. (11, 7, 'Gabriel', $3000.00),
  1550. (12, 9, 'Emilia' , $2000.00),
  1551. (13, 9, 'Michael', $2000.00),
  1552. (14, 9, 'Didi', $1500.00);
  1553. GO
  1554.  
  1555. CREATE TABLE dbo.EmployeesNew
  1556. (
  1557. empid INT NOT NULL PRIMARY KEY,
  1558. hid HIERARCHYID NOT NULL,
  1559. lvl AS hid.GetLevel() PERSISTED,
  1560. empname VARCHAR(25) NOT NULL,
  1561. salary MONEY NOT NULL
  1562. );
  1563. GO
  1564.  
  1565. WITH EmpsRN
  1566. AS
  1567. (
  1568. SELECT empid, mgrid, empname, salary,
  1569. ROW_NUMBER() OVER(PARTITION BY mgrid ORDER BY empid) AS rn
  1570. FROM dbo.EmployeesOld
  1571. ),
  1572. EmpPaths AS
  1573. (
  1574. SELECT empid, mgrid, empname, salary,
  1575. CAST('/' AS VARCHAR(900)) AS cpath
  1576. FROM dbo.EmployeesOld
  1577. WHERE mgrid IS NULL
  1578.  
  1579. UNION ALL
  1580.  
  1581. SELECT C.empid, C.mgrid, C.empname, C.salary,
  1582. CAST(cpath + CAST(C.rn AS VARCHAR(20)) + '/' AS VARCHAR(900))
  1583. FROM EmpPaths AS P
  1584. JOIN EmpsRN AS C
  1585. ON C.mgrid = P.empid
  1586. )
  1587. INSERT INTO dbo.EmployeesNew(empid, empname, salary, hid)
  1588. SELECT empid, empname, salary,
  1589. CAST(cpath AS HIERARCHYID) AS hid
  1590. FROM EmpPaths;
  1591.  
  1592. SELECT REPLICATE(' | ', lvl) + empname AS empname, hid.ToString() AS path
  1593. FROM dbo.EmployeesNew
  1594. ORDER BY hid;
  1595.  
  1596. ---------------------------------------------------------------------
  1597. -- Sorting Separated Lists of Values
  1598. ---------------------------------------------------------------------
  1599.  
  1600. USE tempdb;
  1601. IF OBJECT_ID('dbo.IPs', 'U') IS NOT NULL DROP TABLE dbo.IPs;
  1602.  
  1603. -- Creation script for table IPs
  1604. CREATE TABLE dbo.IPs
  1605. (
  1606. ip varchar(15) NOT NULL,
  1607. CONSTRAINT PK_IPs PRIMARY KEY(ip),
  1608. -- CHECK constraint that validates IPs
  1609. CONSTRAINT CHK_IP_valid CHECK
  1610. (
  1611. -- 3 periods and no empty octets
  1612. ip LIKE '_%._%._%._%'
  1613. AND
  1614. -- not 4 periods or more
  1615. ip NOT LIKE '%.%.%.%.%'
  1616. AND
  1617. -- no characters other than digits and periods
  1618. ip NOT LIKE '%[^0-9.]%'
  1619. AND
  1620. -- not more than 3 digits per octet
  1621. ip NOT LIKE '%[0-9][0-9][0-9][0-9]%'
  1622. AND
  1623. -- NOT 300 - 999
  1624. ip NOT LIKE '%[3-9][0-9][0-9]%'
  1625. AND
  1626. -- NOT 260 - 299
  1627. ip NOT LIKE '%2[6-9][0-9]%'
  1628. AND
  1629. -- NOT 256 - 259
  1630. ip NOT LIKE '%25[6-9]%'
  1631. )
  1632. );
  1633. GO
  1634.  
  1635. -- Sample data
  1636. INSERT INTO dbo.IPs(ip) VALUES
  1637. ('131.107.2.201'),
  1638. ('131.33.2.201'),
  1639. ('131.33.2.202'),
  1640. ('3.107.2.4'),
  1641. ('3.107.3.169'),
  1642. ('3.107.104.172'),
  1643. ('22.107.202.123'),
  1644. ('22.20.2.77'),
  1645. ('22.156.9.91'),
  1646. ('22.156.89.32');
  1647.  
  1648. -- IP Patterns
  1649. IF OBJECT_ID('dbo.IPPatterns') IS NOT NULL DROP VIEW dbo.IPPatterns;
  1650. GO
  1651. CREATE VIEW dbo.IPPatterns
  1652. AS
  1653.  
  1654. SELECT
  1655. REPLICATE('_', N1.n) + '.' + REPLICATE('_', N2.n) + '.'
  1656. + REPLICATE('_', N3.n) + '.' + REPLICATE('_', N4.n) AS pattern,
  1657. N1.n AS l1, N2.n AS l2, N3.n AS l3, N4.n AS l4,
  1658. 1 AS s1, N1.n+2 AS s2, N1.n+N2.n+3 AS s3, N1.n+N2.n+N3.n+4 AS s4
  1659. FROM dbo.Nums AS N1, dbo.Nums AS N2, dbo.Nums AS N3, dbo.Nums AS N4
  1660. WHERE N1.n <= 3 AND N2.n <= 3 AND N3.n <= 3 AND N4.n <= 3;
  1661. GO
  1662.  
  1663. -- Sort IPs, solution 1
  1664. SELECT ip
  1665. FROM dbo.IPs
  1666. JOIN dbo.IPPatterns
  1667. ON ip LIKE pattern
  1668. ORDER BY
  1669. CAST(SUBSTRING(ip, s1, l1) AS TINYINT),
  1670. CAST(SUBSTRING(ip, s2, l2) AS TINYINT),
  1671. CAST(SUBSTRING(ip, s3, l3) AS TINYINT),
  1672. CAST(SUBSTRING(ip, s4, l4) AS TINYINT);
  1673.  
  1674. -- Sort IPs, solution 2
  1675. SELECT ip
  1676. FROM dbo.IPs
  1677. ORDER BY CAST('/' + ip + '/' AS HIERARCHYID);
  1678.  
  1679. -- Generic form of problem
  1680. SET NOCOUNT ON;
  1681. USE tempdb;
  1682. IF OBJECT_ID('dbo.T1', 'U') IS NOT NULL DROP TABLE dbo.T1;
  1683.  
  1684. CREATE TABLE dbo.T1
  1685. (
  1686. id INT NOT NULL IDENTITY PRIMARY KEY,
  1687. val VARCHAR(500) NOT NULL
  1688. );
  1689. GO
  1690.  
  1691. INSERT INTO dbo.T1(val) VALUES
  1692. ('100'),
  1693. ('7,4,250'),
  1694. ('22,40,5,60,4,100,300,478,19710212'),
  1695. ('22,40,5,60,4,99,300,478,19710212'),
  1696. ('22,40,5,60,4,99,300,478,9999999'),
  1697. ('10,30,40,50,20,30,40'),
  1698. ('7,4,250'),
  1699. ('-1'),
  1700. ('-2'),
  1701. ('-11'),
  1702. ('-22'),
  1703. ('-123'),
  1704. ('-321'),
  1705. ('22,40,5,60,4,-100,300,478,19710212'),
  1706. ('22,40,5,60,4,-99,300,478,19710212');
  1707.  
  1708. SELECT id, val
  1709. FROM dbo.T1
  1710. ORDER BY CAST('/' + REPLACE(val, ',', '/') + '/' AS HIERARCHYID);
  1711.  
  1712. ---------------------------------------------------------------------
  1713. -- Nested Sets嵌套集合
  1714. ---------------------------------------------------------------------
  1715.  
  1716. ---------------------------------------------------------------------
  1717. -- Assigning Left and Right Values 分配左值和右值
  1718. ---------------------------------------------------------------------
  1719.  
  1720. -- Producing Binary Sort Paths Representing Nested Sets Relationships
  1721. USE tempdb;
  1722. GO
  1723. -- Create index to speed sorting siblings by empname, empid
  1724. CREATE UNIQUE INDEX idx_unc_mgrid_empname_empid
  1725. ON dbo.Employees(mgrid, empname, empid);
  1726. GO
  1727.  
  1728. DECLARE @root AS INT = 1;
  1729.  
  1730. -- CTE with two numbers: 1 and 2
  1731. WITH TwoNums
  1732. AS
  1733. (
  1734. SELECT n FROM(VALUES(1),(2)) AS D(n)
  1735. ),
  1736. -- CTE with two binary sort paths for each node:
  1737. -- One smaller than descendants sort paths
  1738. -- One greater than descendants sort paths
  1739. SortPath
  1740. AS
  1741. (
  1742. SELECT empid, 0 AS lvl, n,
  1743. CAST(n AS VARBINARY(MAX)) AS sort_path
  1744. FROM dbo.Employees CROSS JOIN TwoNums
  1745. WHERE empid = @root
  1746.  
  1747. UNION ALL
  1748.  
  1749. SELECT C.empid, P.lvl + 1, TN.n,
  1750. P.sort_path + CAST(
  1751. (-1+ROW_NUMBER() OVER(PARTITION BY C.mgrid
  1752. -- *** determines order of siblings ***
  1753. ORDER BY C.empname, C.empid))/2*2+TN.n
  1754. AS BINARY(4))
  1755. FROM SortPath AS P
  1756. JOIN dbo.Employees AS C
  1757. ON P.n = 1
  1758. AND C.mgrid = P.empid
  1759. CROSS JOIN TwoNums AS TN
  1760. )
  1761. SELECT * FROM SortPath
  1762. ORDER BY sort_path;
  1763. GO
  1764.  
  1765. -- CTE Code That Creates Nested Sets Relationships
  1766. DECLARE @root AS INT = 1;
  1767.  
  1768. -- CTE with two numbers: 1 and 2
  1769. WITH TwoNums
  1770. AS
  1771. (
  1772. SELECT n FROM(VALUES(1),(2)) AS D(n)
  1773. ),
  1774. -- CTE with two binary sort paths for each node:
  1775. -- One smaller than descendants sort paths
  1776. -- One greater than descendants sort paths
  1777. SortPath
  1778. AS
  1779. (
  1780. SELECT empid, 0 AS lvl, n,
  1781. CAST(n AS VARBINARY(MAX)) AS sort_path
  1782. FROM dbo.Employees CROSS JOIN TwoNums
  1783. WHERE empid = @root
  1784.  
  1785. UNION ALL
  1786.  
  1787. SELECT C.empid, P.lvl + 1, TN.n,
  1788. P.sort_path + CAST(
  1789. ROW_NUMBER() OVER(PARTITION BY C.mgrid
  1790. -- *** determines order of siblings ***
  1791. ORDER BY C.empname, C.empid, TN.n)
  1792. AS BINARY(4))
  1793. FROM SortPath AS P
  1794. JOIN dbo.Employees AS C
  1795. ON P.n = 1
  1796. AND C.mgrid = P.empid
  1797. CROSS JOIN TwoNums AS TN
  1798. ),
  1799. -- CTE with Row Numbers Representing sort_path Order
  1800. Sort
  1801. AS
  1802. (
  1803. SELECT empid, lvl,
  1804. ROW_NUMBER() OVER(ORDER BY sort_path) AS sortval
  1805. FROM SortPath
  1806. ),
  1807. -- CTE with Left and Right Values Representing
  1808. -- Nested Sets Relationships
  1809. NestedSets
  1810. AS
  1811. (
  1812. SELECT empid, lvl, MIN(sortval) AS lft, MAX(sortval) AS rgt
  1813. FROM Sort
  1814. GROUP BY empid, lvl
  1815. )
  1816. SELECT * FROM NestedSets
  1817. ORDER BY lft;
  1818. GO
  1819.  
  1820. -- Materializing Nested Sets Relationships in a Table
  1821. SET NOCOUNT ON;
  1822. USE tempdb;
  1823. GO
  1824.  
  1825. DECLARE @root AS INT = 1;
  1826.  
  1827. WITH TwoNums
  1828. AS
  1829. (
  1830. SELECT n FROM(VALUES(1),(2)) AS D(n)
  1831. ),
  1832. SortPath
  1833. AS
  1834. (
  1835. SELECT empid, 0 AS lvl, n,
  1836. CAST(n AS VARBINARY(MAX)) AS sort_path
  1837. FROM dbo.Employees CROSS JOIN TwoNums
  1838. WHERE empid = @root
  1839.  
  1840. UNION ALL
  1841.  
  1842. SELECT C.empid, P.lvl + 1, TN.n,
  1843. P.sort_path + CAST(
  1844. ROW_NUMBER() OVER(PARTITION BY C.mgrid
  1845. -- *** determines order of siblings ***
  1846. ORDER BY C.empname, C.empid, TN.n)
  1847. AS BINARY(4))
  1848. FROM SortPath AS P
  1849. JOIN dbo.Employees AS C
  1850. ON P.n = 1
  1851. AND C.mgrid = P.empid
  1852. CROSS JOIN TwoNums AS TN
  1853. ),
  1854. Sort
  1855. AS
  1856. (
  1857. SELECT empid, lvl,
  1858. ROW_NUMBER() OVER(ORDER BY sort_path) AS sortval
  1859. FROM SortPath
  1860. ),
  1861. NestedSets
  1862. AS
  1863. (
  1864. SELECT empid, lvl, MIN(sortval) AS lft, MAX(sortval) AS rgt
  1865. FROM Sort
  1866. GROUP BY empid, lvl
  1867. )
  1868. SELECT E.empid, E.empname, E.salary, NS.lvl, NS.lft, NS.rgt
  1869. INTO dbo.EmployeesNS
  1870. FROM NestedSets AS NS
  1871. JOIN dbo.Employees AS E
  1872. ON E.empid = NS.empid;
  1873.  
  1874. ALTER TABLE dbo.EmployeesNS ADD PRIMARY KEY NONCLUSTERED(empid);
  1875. CREATE UNIQUE CLUSTERED INDEX idx_unc_lft_rgt ON dbo.EmployeesNS(lft, rgt);
  1876. GO
  1877.  
  1878. ---------------------------------------------------------------------
  1879. -- Querying
  1880. ---------------------------------------------------------------------
  1881.  
  1882. -- Descendants of a given root
  1883. SELECT C.empid, REPLICATE(' | ', C.lvl - P.lvl) + C.empname AS empname
  1884. FROM dbo.EmployeesNS AS P
  1885. JOIN dbo.EmployeesNS AS C
  1886. ON P.empid = 3
  1887. AND C.lft >= P.lft AND C.rgt <= P.rgt
  1888. ORDER BY C.lft;
  1889.  
  1890. -- Descendants of a given root, limiting 2 levels
  1891. SELECT C.empid, REPLICATE(' | ', C.lvl - P.lvl) + C.empname AS empname
  1892. FROM dbo.EmployeesNS AS P
  1893. JOIN dbo.EmployeesNS AS C
  1894. ON P.empid = 3
  1895. AND C.lft >= P.lft AND C.rgt <= P.rgt
  1896. AND C.lvl - P.lvl <= 2
  1897. ORDER BY C.lft;
  1898.  
  1899. -- Leaf nodes under a given root
  1900. SELECT C.empid, C.empname
  1901. FROM dbo.EmployeesNS AS P
  1902. JOIN dbo.EmployeesNS AS C
  1903. ON P.empid = 3
  1904. AND C.lft >= P.lft AND C.rgt <= P.rgt
  1905. WHERE C.rgt - C.lft = 1;
  1906.  
  1907. -- Count of subordinates of each node
  1908. SELECT empid, (rgt - lft - 1) / 2 AS cnt,
  1909. REPLICATE(' | ', lvl) + empname AS empname
  1910. FROM dbo.EmployeesNS
  1911. ORDER BY lft;
  1912.  
  1913. -- Ancestors of a given node
  1914. SELECT P.empid, P.empname, P.lvl
  1915. FROM dbo.EmployeesNS AS P
  1916. JOIN dbo.EmployeesNS AS C
  1917. ON C.empid = 14
  1918. AND C.lft >= P.lft AND C.rgt <= P.rgt;
  1919. GO
  1920.  
  1921. -- Cleanup
  1922. DROP TABLE dbo.EmployeesNS;
  1923. GO
  1924.  
  1925. ---------------------------------------------------------------------
  1926. -- Transitive Closure
  1927. ---------------------------------------------------------------------
  1928.  
  1929. ---------------------------------------------------------------------
  1930. -- Directed Acyclic Graph (DAG)
  1931. ---------------------------------------------------------------------
  1932.  
  1933. -- Transitive Closure of BOM (DAG)
  1934. WITH BOMTC
  1935. AS
  1936. (
  1937. -- Return all first-level containment relationships
  1938. SELECT assemblyid, partid
  1939. FROM dbo.BOM
  1940. WHERE assemblyid IS NOT NULL
  1941.  
  1942. UNION ALL
  1943.  
  1944. -- Return next-level containment relationships
  1945. SELECT P.assemblyid, C.partid
  1946. FROM BOMTC AS P
  1947. JOIN dbo.BOM AS C
  1948. ON C.assemblyid = P.partid
  1949. )
  1950. -- Return distinct pairs that have
  1951. -- transitive containment relationships
  1952. SELECT DISTINCT assemblyid, partid
  1953. FROM BOMTC;
  1954. GO
  1955.  
  1956. -- Listing 12-4: Creation Script for the BOMTC UDF
  1957. IF OBJECT_ID('dbo.BOMTC') IS NOT NULL
  1958. DROP FUNCTION dbo.BOMTC;
  1959. GO
  1960.  
  1961. CREATE FUNCTION BOMTC() RETURNS @BOMTC TABLE
  1962. (
  1963. assemblyid INT NOT NULL,
  1964. partid INT NOT NULL,
  1965. PRIMARY KEY (assemblyid, partid)
  1966. )
  1967. AS
  1968. BEGIN
  1969. INSERT INTO @BOMTC(assemblyid, partid)
  1970. SELECT assemblyid, partid
  1971. FROM dbo.BOM
  1972. WHERE assemblyid IS NOT NULL
  1973.  
  1974. WHILE @@rowcount > 0
  1975. INSERT INTO @BOMTC
  1976. SELECT P.assemblyid, C.partid
  1977. FROM @BOMTC AS P
  1978. JOIN dbo.BOM AS C
  1979. ON C.assemblyid = P.partid
  1980. WHERE NOT EXISTS
  1981. (SELECT * FROM @BOMTC AS P2
  1982. WHERE P2.assemblyid = P.assemblyid
  1983. AND P2.partid = C.partid);
  1984.  
  1985. RETURN;
  1986. END
  1987. GO
  1988.  
  1989. -- Use the BOMTC UDF
  1990. SELECT assemblyid, partid FROM BOMTC();
  1991. GO
  1992.  
  1993. -- All Paths in BOM
  1994. WITH BOMPaths
  1995. AS
  1996. (
  1997. SELECT assemblyid, partid,
  1998. 1 AS distance, -- distance in first level is 1
  1999. -- path in first level is .assemblyid.partid.
  2000. '.' + CAST(assemblyid AS VARCHAR(MAX)) +
  2001. '.' + CAST(partid AS VARCHAR(MAX)) + '.' AS path
  2002. FROM dbo.BOM
  2003. WHERE assemblyid IS NOT NULL
  2004.  
  2005. UNION ALL
  2006.  
  2007. SELECT P.assemblyid, C.partid,
  2008. -- distance in next level is parent's distance + 1
  2009. P.distance + 1,
  2010. -- path in next level is parent_path.child_partid.
  2011. P.path + CAST(C.partid AS VARCHAR(MAX)) + '.'
  2012. FROM BOMPaths AS P
  2013. JOIN dbo.BOM AS C
  2014. ON C.assemblyid = P.partid
  2015. )
  2016. -- Return all paths
  2017. SELECT * FROM BOMPaths;
  2018.  
  2019. -- Shortest Paths in BOM
  2020. WITH BOMPaths -- All paths
  2021. AS
  2022. (
  2023. SELECT assemblyid, partid,
  2024. 1 AS distance,
  2025. '.' + CAST(assemblyid AS VARCHAR(MAX)) +
  2026. '.' + CAST(partid AS VARCHAR(MAX)) + '.' AS path
  2027. FROM dbo.BOM
  2028. WHERE assemblyid IS NOT NULL
  2029.  
  2030. UNION ALL
  2031.  
  2032. SELECT P.assemblyid, C.partid,
  2033. P.distance + 1,
  2034. P.path + CAST(C.partid AS VARCHAR(MAX)) + '.'
  2035. FROM BOMPaths AS P
  2036. JOIN dbo.BOM AS C
  2037. ON C.assemblyid = P.partid
  2038. ),
  2039. BOMMinDist AS -- Minimum distance for each pair
  2040. (
  2041. SELECT assemblyid, partid, MIN(distance) AS mindist
  2042. FROM BOMPaths
  2043. GROUP BY assemblyid, partid
  2044. )
  2045. -- Shortest path for each pair
  2046. SELECT BP.*
  2047. FROM BOMMinDist AS BMD
  2048. JOIN BOMPaths AS BP
  2049. ON BMD.assemblyid = BP.assemblyid
  2050. AND BMD.partid = BP.partid
  2051. AND BMD.mindist = BP.distance;
  2052. GO
  2053.  
  2054. ---------------------------------------------------------------------
  2055. -- Undirected Cyclic Graph
  2056. ---------------------------------------------------------------------
  2057.  
  2058. -- Transitive Closure of Roads (Undirected Cyclic Graph)
  2059. WITH Roads2 -- Two rows for each pair (f-->t, t-->f)
  2060. AS
  2061. (
  2062. SELECT city1 AS from_city, city2 AS to_city FROM dbo.Roads
  2063. UNION ALL
  2064. SELECT city2, city1 FROM dbo.Roads
  2065. ),
  2066. RoadPaths AS
  2067. (
  2068. -- Return all first-level reachability pairs
  2069. SELECT from_city, to_city,
  2070. -- path is needed to identify cycles
  2071. CAST('.' + from_city + '.' + to_city + '.' AS VARCHAR(MAX)) AS path
  2072. FROM Roads2
  2073.  
  2074. UNION ALL
  2075.  
  2076. -- Return next-level reachability pairs
  2077. SELECT F.from_city, T.to_city,
  2078. CAST(F.path + T.to_city + '.' AS VARCHAR(MAX))
  2079. FROM RoadPaths AS F
  2080. JOIN Roads2 AS T
  2081. -- if to_city appears in from_city's path, cycle detected
  2082. ON CASE WHEN F.path LIKE '%.' + T.to_city + '.%'
  2083. THEN 1 ELSE 0 END = 0
  2084. AND F.to_city = T.from_city
  2085. )
  2086. -- Return Transitive Closure of Roads
  2087. SELECT DISTINCT from_city, to_city
  2088. FROM RoadPaths;
  2089. GO
  2090.  
  2091. -- Creation Script for the RoadsTC UDF
  2092. IF OBJECT_ID('dbo.RoadsTC') IS NOT NULL
  2093. DROP FUNCTION dbo.RoadsTC;
  2094. GO
  2095.  
  2096. CREATE FUNCTION dbo.RoadsTC() RETURNS @RoadsTC TABLE (
  2097. from_city VARCHAR(3) NOT NULL,
  2098. to_city VARCHAR(3) NOT NULL,
  2099. PRIMARY KEY (from_city, to_city)
  2100. )
  2101. AS
  2102. BEGIN
  2103. DECLARE @added as INT;
  2104.  
  2105. INSERT INTO @RoadsTC(from_city, to_city)
  2106. SELECT city1, city2 FROM dbo.Roads;
  2107.  
  2108. SET @added = @@rowcount;
  2109.  
  2110. INSERT INTO @RoadsTC
  2111. SELECT city2, city1 FROM dbo.Roads
  2112.  
  2113. SET @added = @added + @@rowcount;
  2114.  
  2115. WHILE @added > 0 BEGIN
  2116.  
  2117. INSERT INTO @RoadsTC
  2118. SELECT DISTINCT TC.from_city, R.city2
  2119. FROM @RoadsTC AS TC
  2120. JOIN dbo.Roads AS R
  2121. ON R.city1 = TC.to_city
  2122. WHERE NOT EXISTS
  2123. (SELECT * FROM @RoadsTC AS TC2
  2124. WHERE TC2.from_city = TC.from_city
  2125. AND TC2.to_city = R.city2)
  2126. AND TC.from_city <> R.city2;
  2127.  
  2128. SET @added = @@rowcount;
  2129.  
  2130. INSERT INTO @RoadsTC
  2131. SELECT DISTINCT TC.from_city, R.city1
  2132. FROM @RoadsTC AS TC
  2133. JOIN dbo.Roads AS R
  2134. ON R.city2 = TC.to_city
  2135. WHERE NOT EXISTS
  2136. (SELECT * FROM @RoadsTC AS TC2
  2137. WHERE TC2.from_city = TC.from_city
  2138. AND TC2.to_city = R.city1)
  2139. AND TC.from_city <> R.city1;
  2140.  
  2141. SET @added = @added + @@rowcount;
  2142. END
  2143. RETURN;
  2144. END
  2145. GO
  2146.  
  2147. -- Use the RoadsTC UDF
  2148. SELECT * FROM dbo.RoadsTC();
  2149. GO
  2150.  
  2151. -- All paths and distances in Roads (15262 rows)
  2152. WITH Roads2
  2153. AS
  2154. (
  2155. SELECT city1 AS from_city, city2 AS to_city, distance FROM dbo.Roads
  2156. UNION ALL
  2157. SELECT city2, city1, distance FROM dbo.Roads
  2158. ),
  2159. RoadPaths AS
  2160. (
  2161. SELECT from_city, to_city, distance,
  2162. CAST('.' + from_city + '.' + to_city + '.' AS VARCHAR(MAX)) AS path
  2163. FROM Roads2
  2164.  
  2165. UNION ALL
  2166.  
  2167. SELECT F.from_city, T.to_city, F.distance + T.distance,
  2168. CAST(F.path + T.to_city + '.' AS VARCHAR(MAX))
  2169. FROM RoadPaths AS F
  2170. JOIN Roads2 AS T
  2171. ON CASE WHEN F.path LIKE '%.' + T.to_city + '.%'
  2172. THEN 1 ELSE 0 END = 0
  2173. AND F.to_city = T.from_city
  2174. )
  2175. -- Return all paths and distances
  2176. SELECT * FROM RoadPaths;
  2177.  
  2178. -- Shortest paths in Roads
  2179. WITH Roads2
  2180. AS
  2181. (
  2182. SELECT city1 AS from_city, city2 AS to_city, distance FROM dbo.Roads
  2183. UNION ALL
  2184. SELECT city2, city1, distance FROM dbo.Roads
  2185. ),
  2186. RoadPaths AS
  2187. (
  2188. SELECT from_city, to_city, distance,
  2189. CAST('.' + from_city + '.' + to_city + '.' AS VARCHAR(MAX)) AS path
  2190. FROM Roads2
  2191.  
  2192. UNION ALL
  2193.  
  2194. SELECT F.from_city, T.to_city, F.distance + T.distance,
  2195. CAST(F.path + T.to_city + '.' AS VARCHAR(MAX))
  2196. FROM RoadPaths AS F
  2197. JOIN Roads2 AS T
  2198. ON CASE WHEN F.path LIKE '%.' + T.to_city + '.%'
  2199. THEN 1 ELSE 0 END = 0
  2200. AND F.to_city = T.from_city
  2201. ),
  2202. RoadsMinDist -- Min distance for each pair in TC
  2203. AS
  2204. (
  2205. SELECT from_city, to_city, MIN(distance) AS mindist
  2206. FROM RoadPaths
  2207. GROUP BY from_city, to_city
  2208. )
  2209. -- Return shortest paths and distances
  2210. SELECT RP.*
  2211. FROM RoadsMinDist AS RMD
  2212. JOIN RoadPaths AS RP
  2213. ON RMD.from_city = RP.from_city
  2214. AND RMD.to_city = RP.to_city
  2215. AND RMD.mindist = RP.distance;
  2216. GO
  2217.  
  2218. -- Load Shortest Road Paths Into a Table
  2219. WITH Roads2
  2220. AS
  2221. (
  2222. SELECT city1 AS from_city, city2 AS to_city, distance FROM dbo.Roads
  2223. UNION ALL
  2224. SELECT city2, city1, distance FROM dbo.Roads
  2225. ),
  2226. RoadPaths AS
  2227. (
  2228. SELECT from_city, to_city, distance,
  2229. CAST('.' + from_city + '.' + to_city + '.' AS VARCHAR(MAX)) AS path
  2230. FROM Roads2
  2231.  
  2232. UNION ALL
  2233.  
  2234. SELECT F.from_city, T.to_city, F.distance + T.distance,
  2235. CAST(F.path + T.to_city + '.' AS VARCHAR(MAX))
  2236. FROM RoadPaths AS F
  2237. JOIN Roads2 AS T
  2238. ON CASE WHEN F.path LIKE '%.' + T.to_city + '.%'
  2239. THEN 1 ELSE 0 END = 0
  2240. AND F.to_city = T.from_city
  2241. ),
  2242. RoadsMinDist
  2243. AS
  2244. (
  2245. SELECT from_city, to_city, MIN(distance) AS mindist
  2246. FROM RoadPaths
  2247. GROUP BY from_city, to_city
  2248. )
  2249. SELECT RP.*
  2250. INTO dbo.RoadPaths
  2251. FROM RoadsMinDist AS RMD
  2252. JOIN RoadPaths AS RP
  2253. ON RMD.from_city = RP.from_city
  2254. AND RMD.to_city = RP.to_city
  2255. AND RMD.mindist = RP.distance;
  2256.  
  2257. CREATE UNIQUE CLUSTERED INDEX idx_uc_from_city_to_city
  2258. ON dbo.RoadPaths(from_city, to_city);
  2259. GO
  2260.  
  2261. -- Return shortest path between Los Angeles and New York
  2262. SELECT * FROM dbo.RoadPaths
  2263. WHERE from_city = 'LAX' AND to_city = 'JFK';
  2264. GO
  2265.  
  2266. -- Creation Script for the RoadsTC UDF
  2267. IF OBJECT_ID('dbo.RoadsTC') IS NOT NULL
  2268. DROP FUNCTION dbo.RoadsTC;
  2269. GO
  2270. CREATE FUNCTION dbo.RoadsTC() RETURNS @RoadsTC TABLE
  2271. (
  2272. uniquifier INT NOT NULL IDENTITY,
  2273. from_city VARCHAR(3) NOT NULL,
  2274. to_city VARCHAR(3) NOT NULL,
  2275. distance INT NOT NULL,
  2276. route VARCHAR(MAX) NOT NULL,
  2277. PRIMARY KEY (from_city, to_city, uniquifier)
  2278. )
  2279. AS
  2280. BEGIN
  2281. DECLARE @added AS INT;
  2282.  
  2283. INSERT INTO @RoadsTC
  2284. SELECT city1 AS from_city, city2 AS to_city, distance,
  2285. '.' + city1 + '.' + city2 + '.'
  2286. FROM dbo.Roads;
  2287.  
  2288. SET @added = @@rowcount;
  2289.  
  2290. INSERT INTO @RoadsTC
  2291. SELECT city2, city1, distance, '.' + city2 + '.' + city1 + '.'
  2292. FROM dbo.Roads;
  2293.  
  2294. SET @added = @added + @@rowcount;
  2295.  
  2296. WHILE @added > 0 BEGIN
  2297. INSERT INTO @RoadsTC
  2298. SELECT DISTINCT TC.from_city, R.city2,
  2299. TC.distance + R.distance, TC.route + city2 + '.'
  2300. FROM @RoadsTC AS TC
  2301. JOIN dbo.Roads AS R
  2302. ON R.city1 = TC.to_city
  2303. WHERE NOT EXISTS
  2304. (SELECT * FROM @RoadsTC AS TC2
  2305. WHERE TC2.from_city = TC.from_city
  2306. AND TC2.to_city = R.city2
  2307. AND TC2.distance <= TC.distance + R.distance)
  2308. AND TC.from_city <> R.city2;
  2309.  
  2310. SET @added = @@rowcount;
  2311.  
  2312. INSERT INTO @RoadsTC
  2313. SELECT DISTINCT TC.from_city, R.city1,
  2314. TC.distance + R.distance, TC.route + city1 + '.'
  2315. FROM @RoadsTC AS TC
  2316. JOIN dbo.Roads AS R
  2317. ON R.city2 = TC.to_city
  2318. WHERE NOT EXISTS
  2319. (SELECT * FROM @RoadsTC AS TC2
  2320. WHERE TC2.from_city = TC.from_city
  2321. AND TC2.to_city = R.city1
  2322. AND TC2.distance <= TC.distance + R.distance)
  2323. AND TC.from_city <> R.city1;
  2324.  
  2325. SET @added = @added + @@rowcount;
  2326. END
  2327. RETURN;
  2328. END
  2329. GO
  2330.  
  2331. -- Return shortest paths and distances
  2332. SELECT from_city, to_city, distance, route
  2333. FROM (SELECT from_city, to_city, distance, route,
  2334. RANK() OVER (PARTITION BY from_city, to_city
  2335. ORDER BY distance) AS rk
  2336. FROM dbo.RoadsTC()) AS RTC
  2337. WHERE rk = 1;
  2338. GO
  2339.  
  2340. -- Cleanup
  2341. DROP TABLE dbo.RoadPaths;
  2342. GO

  

sql script: Graphs, Trees, Hierarchies and Recursive Queries的更多相关文章

  1. sql server: Graphs, Trees, Hierarchies and Recursive Queries

    --------------------------------------------------------------------- -- Chapter 09 - Graphs, Trees, ...

  2. 可重复执行的SQL Script

    问题 在工作中偶尔会遇到这样的问题:SQL script重复执行时会报错. 理想的状态下,SQL script跑一遍就够了,是不会重复执行的,但是实际情况往往很复杂. 比如Dev同学在开发时在A环境把 ...

  3. How to Enable Trace or Debug for APIs executed as SQL Script Outside of the Applications ?

    In this Document   Goal   Solution   1: How do you enable trace for an API when executed from a SQL ...

  4. MySQL5.7: sql script demo

    -- MyISAM Foreign Keys显示不了外键,MyISAM此为5.0 以下版本使用 InnoDB 为5.0以上版本使用 drop table IF EXISTS city; CREATE ...

  5. SQLite: sql script demo

    如果有成熟的架构,如何根据数据库关系的表.视图等,进行代码生成架构?减少写代码的时间? -- 考虑主键外键 -- create database geovindu; use geovindu; --2 ...

  6. csharp:SMO run sql script

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  7. How to import .sql script

    How to import .sql script 1.Export .sql from pl/sql developer you can reference to other document in ...

  8. Codeforces 932.B Recursive Queries

    B. Recursive Queries time limit per test 2 seconds memory limit per test 256 megabytes input standar ...

  9. doris: shell invoke .sql script for doris and passing values for parameters in sql script.

    1. background in most cases, we want to execute sql script  in doris  routinely. using azkaban, to l ...

随机推荐

  1. 几种简单的编码(为什么使用ASCII码)

    二-十进制码(BCD码) 在目前的数字系统中,一般是采用二进制数进行运算的,但是由于人们习惯采用十进制数,因此常需进行十进制数和二进制数之间的转换,其转换方法上面已讨论过了.为了便于数字系统处理十进制 ...

  2. TextView展示富文本时emoj或图片和文字不对齐的解决方案

    在项目中,回复框.聊天界面的显示往往会有emoj或者图片,但是一个比较头疼的问题是,会出现emoj表情或者图片和文字的位置不对齐,总是有偏移,这样很影响用户体验的.下面会总结一下如何解决这个问题. 本 ...

  3. 我自己的sublime3环境

    概述 我本来一直用的别人自带的破解版sublime3,自带插件. 前几天看<程序员修炼之道>,其中谈到了最好精通一种编辑器,我觉得说的很有道理,于是重新下了最新版的sublime3,一步步 ...

  4. JS应用实例6:二级联动

    本案例很常用,应用场景:注册页面填写籍贯,省市二级联动 总体思想:创建一个二维数组存入省市,获取选中的省份并比较,创建标签遍历添加 代码: <!DOCTYPE html> <html ...

  5. 报警系统:php输出头信息以方便脚本抓取信息[排查篇]

    做监控系统时,需要对某个页面进行监控,可以通过很多方式进行报警,如:正常则输出一个规定的变量,错误时则不输出.但是还有一个更为方便的做法,就是当前错误时,直接使用header抛出信息,如: heade ...

  6. [git] 基本原理

    1. 基本原理 工作目录:本地项目所在目录    暂存区: 被 git 所管理的文件 本地仓库:本地的版本仓库,一般的提交操作会将变更信息先提交到本地仓库的版本库中 远程仓库:远程的版本仓库,将变更信 ...

  7. JS  实现九宫格算法

    九宫格算法核心: 利用控件索引index计算出控件所在的行数和列数: 利用控件计算出left距离: 利用控件计算出top距离: 写特效时需要用到定位 公式: 行 row=parseInt(i/cols ...

  8. oracle中查询用户表/索引/视图创建语句

    不多说,直接上干货 1.查询当前用户下表的创建语句 select dbms_metadata.get_ddl('TABLE','ux_future') from dual; 2.查询其他用户下表的创建 ...

  9. await和async在一般处理程序中的使用

    写在前面 有个小项目,前端使用的html页面,那服务端的业务处理就采用最简单的一般处理程序了,之前一直在用,觉得一直用一种方式,确实挺蛋疼的,之前也有了解过async和await的内容.就想着自己折腾 ...

  10. 读vue-0.6-text-parser.js源码

    提取字符串中的表达式 var BINDING_RE = /\{\{(.+?)\}\}/; function parse(text) { // 找不到返回null if (!BINDING_RE.tes ...