A common requirement when dealing with tables with recursive relationship where a record points to another (parent) record in the same table, is to get the full path of the record name. like the case with full folder paths in a file system hierarchy.
For example: (data from http://www.ida.liu.se/~iislab/projects/secont/main/)
 
For the above data, we need to get this:
 
This can be achieved using Recursive Common Table Expressions:
It works like this:
For example: (data from http://www.ida.liu.se/~iislab/projects/secont/main/)
| CategoryId | Name | ParentCategoryId | 
| 1 | Asset | NULL | 
| 2 | Countermeasure | NULL | 
| 3 | Cryptography | 2 | 
| 4 | Encryption | 3 | 
| 5 | SignatureAlgorithm | 4 | 
| 6 | CryptographicHashFunction | 5 | 
| 7 | DSA | 6 | 
| 8 | MD5 | 6 | 
| 9 | EncryptionAlgorithm | 4 | 
| 10 | BlockCipher | 9 | 
| 11 | AES | 10 | 
| 12 | DES | 10 | 
For the above data, we need to get this:
| CategoryId | Name | 
| 1 | Asset | 
| 2 | Countermeasure | 
| 3 | Countermeasure > Cryptography | 
| 4 | Countermeasure > Cryptography > Encryption | 
| 5 | Countermeasure > Cryptography > Encryption > SignatureAlgorithm | 
| 9 | Countermeasure > Cryptography > Encryption > EncryptionAlgorithm | 
| 10 | Countermeasure > Cryptography > Encryption > EncryptionAlgorithm > BlockCipher | 
| 11 | Countermeasure > Cryptography > Encryption > EncryptionAlgorithm > BlockCipher > AES | 
| 12 | Countermeasure > Cryptography > Encryption > EncryptionAlgorithm > BlockCipher > DES | 
| 6 | Countermeasure > Cryptography > Encryption > SignatureAlgorithm > CryptographicHashFunction | 
| 7 | Countermeasure > Cryptography > Encryption > SignatureAlgorithm > CryptographicHashFunction > DSA | 
| 8 | Countermeasure > Cryptography > Encryption > SignatureAlgorithm > CryptographicHashFunction > MD5 | 
This can be achieved using Recursive Common Table Expressions:
WITH CategoryCTE
AS
(
SELECT C.CategoryId, CONVERT(NVARCHAR(500), C.Name) AS Name FROM dbo.Category C
WHERE ParentCategoryId IS NULL
UNION ALL
SELECT C.CategoryId, CONVERT(NVARCHAR(500), CTE.name + N' > ' + C.Name) AS Name FROM dbo.Category C
JOIN CategoryCTE CTE ON C.ParentCategoryId = CTE.CategoryId
)
SELECT * FROM CategoryCTE It works like this:
- The CTE selects from the base table data, the level which has no parents
- The result is union-ed with the recursive part, which joins the base table with the last value of the CTE up to the current level of recursion
- The name field of a record is a concatenation between its parent name and its own name
- Select the result of the CTE
 
