SQL : How to get full hierarchy paths avoiding self & back relations? - sql

I have the following table (StatusTransitions) :: SQL Fiddle
I use the below queries to get all full hierarchy paths ::
;WITH Paths AS
(
SELECT
ID, FromID, ToID,
CAST(FromID + ',' + CAST(ToID AS VARCHAR(100)) AS varchar(100)) AS [Path]
, 1 as LevelID
FROM StatusTransitions
WHERE FromID = 'A'
UNION ALL
SELECT
NextTransition.ID, NextTransition.FromID, NextTransition.ToID,
CAST(PreviousTransition.[Path] + ',' + CAST( NextTransition.ToID AS VARCHAR(100)) AS varchar(100)) AS [Path]
,(PreviousTransition.LevelID + 1) as LevelID
FROM
StatusTransitions AS NextTransition
join Paths AS PreviousTransition ON NextTransition.FromID = PreviousTransition.ToID
)
SELECT ID, FromID, ToID, [Path], LevelID
FROM Paths
WHERE ToID NOT IN
(
SELECT FromID
FROM StatusTransitions
WHERE FromID <> 'A'
)
Order By ID
OPTION (MAXRECURSION 20)
Query work perfectly in case we don't have any self relation between the same item or any back relation ( example :: items with id 12,13 ) ..
previous relations go into infinite loops ..
How can change this queries in which be able to avoid these relations ??

You prevent cycles by being sure that the new element in the path is not already in the path. The following where statement does this:
WHERE ','+PreviousTransition.[Path]+',' not like '%,'+NextTransition.ToID+',%'
The complete query is:
;WITH Paths AS
(
SELECT
ID, FromID, ToID,
CAST(FromID + ',' + CAST(ToID AS VARCHAR(100)) AS varchar(100)) AS [Path]
, 1 AS LevelID
FROM StatusTransitions
WHERE FromID = 'A'
UNION ALL
SELECT
NextTransition.ID, NextTransition.FromID, NextTransition.ToID,
CAST(PreviousTransition.[Path] + ',' + CAST( NextTransition.ToID AS VARCHAR(100)) AS varchar(100)) AS [Path],
(PreviousTransition.LevelID + 1) AS LevelID
FROM StatusTransitions NextTransition JOIN
Paths PreviousTransition
ON NextTransition.FromID = PreviousTransition.ToID
WHERE ','+PreviousTransition.[Path]+',' not like '%,'+NextTransition.ToID+',%'
)
SELECT ID, FromID, ToID, [Path], LevelID
FROM Paths
WHERE ToID NOT IN
(
SELECT FromID
FROM StatusTransitions
WHERE FromID <> 'A'
)
ORDER BY ID
OPTION (MAXRECURSION 20);
Because of the not in filtering, the result is not the same as the query on the original data, but I believe it is working correctly. The SQL Fiddle is here.

Related

T-SQL Recursive Query - Data Lineage

Given the following table
I need to get "data lineage" of the database objects.
The expected output (something like that):
This is the script I've tried.
CREATE TABLE #smth
(
ParObj NVARCHAR(200)
,ChilObj NVARCHAR(200)
);
INSERT INTO #smth (
ParObj
,ChilObj
)
VALUES ( N'FactSales', N'qryFactSales' )
,( 'qryFactSales', 'qryFactSalesOnlineUnited' );
WITH cte
AS (
SELECT ParObj
,ChilObj
,level = 1
,path = CAST('root' AS VARCHAR(100))
FROM #smth
UNION ALL
SELECT t2.ParObj
,t2.ChilObj
,level = t1.level + 1
,Path = CAST(t1.path + ' ||| ' + CAST(t2.ChilObj AS VARCHAR(100)) AS VARCHAR(100))
FROM #smth AS t2
JOIN cte AS t1
ON t1.ChilObj = t2.ParObj
)
SELECT *
FROM cte
ORDER BY cte.path;
Is it even achievable somehow?
This version does what you want:
with cte as (
select parobj as obj, convert(nvarchar(max), NULL) as path
from smth
where not exists (select 1 from smth smth2 where smth2.chilobj = smth.parobj)
union all
select smth.chilobj as obj, convert(nvarchar(max), coalesce(path + ' -> ', '')) + cte.obj
from cte join
smth
on cte.obj = smth.parobj
)
select obj, coalesce(path + ' -> ' + obj, 'Root')
from cte;
Here is a db<>fiddle.

Combine Multi Rows with COALESCE

Using SQL-Server 2012
I have the following Table:
Id Description
6192 Salzburg
6193 Salzburg
6194 Salzburg
6196 Innsbruck
6197 Innsbruck
6198 Innsbruck
6199 Innsbruck
6201 Bregenz
6202 Bregenz
6203 Bregenz
I want to Select each Distinct "Description" with all the Id's together in one string:
Description Ids
Salzburg '6192,6193,6194'
Innsbruck '6196,6197,6198'
I saw some similar code on this site [How to concatenate text from multiple rows into a single text string in SQL server?, but I couldn't figure it out yet for my purpose (don't want to use XML Path!). Here is what I have tried so far:
DECLARE #ids AS Nvarchar(MAX)
SELECT #ids = COALESCE(#ids + ',', '') + CAST(t.Id AS nvarchar(5))
FROM (SELECT tmp.Id FROM (SELECT id, [Description] FROM tblMasterPropValues WHERE IdCategory = 253 AND IsActive = 1) as tmp
WHERE [Description] = tmp.[Description]) AS t
SELECT #ids
--SELECT DISTINCT [Description], #ids AS IDs FROM tblMasterPropValues WHERE IdCategory = 253 AND IsActive = 1 AND Id IN (#ids)
I can't really get my head around it, and would appreciate any help on it.
You can try using STUFF() function
SELECT description, Ids = STUFF(
(SELECT ',' + Id
FROM tblMasterPropValues t1
WHERE t1.description = t2.description
FOR XML PATH (''))
, 1, 1, '') from tblMasterPropValues t2
group by description;
For that FOR XML PATH() is the right clause so, you can do :
SELECT DISTINCT v.description, STUFF(v1.ids, 1, 1, '''') + ''''
FROM tblMasterPropValues v CROSS APPLY
(SELECT ', '+ CAST(v1.Id AS VARCHAR(255))
FROM tblMasterPropValues v1
WHERE v1.description = v.description
FOR XML PATH('')
) v1(ids);
You can also make it by using recursive CTE
DECLARE #tblMasterPropValues TABLE (Id INT, Description VARCHAR(20))
INSERT INTO #tblMasterPropValues VALUES
(6192 , 'Salzburg'),
(6193 , 'Salzburg'),
(6194 , 'Salzburg'),
(6196 , 'Innsbruck'),
(6197 , 'Innsbruck'),
(6198 , 'Innsbruck'),
(6199 , 'Innsbruck'),
(6201 , 'Bregenz'),
(6202 , 'Bregenz'),
(6203 , 'Bregenz')
;WITH Tbl AS
(
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY Description ORDER BY Id) AS RN,
COUNT(*) OVER(PARTITION BY Description) AS CNT
FROM #tblMasterPropValues
)
, Rcr AS (
SELECT *, CAST(Id AS varchar(max)) Ids
FROM Tbl WHERE RN = 1
UNION ALL
SELECT T.*, Rcr.Ids + ',' + CAST(T.Id AS VARCHAR(10)) Ids
FROM Rcr
INNER JOIN Tbl T ON T.RN = Rcr.RN + 1 and Rcr.Description = T.Description
)
SELECT RN, Description, Ids FROM Rcr
WHERE RN = CNT
Result:
Description Ids
-------------------- -----------------------
Salzburg 6192,6193,6194
Innsbruck 6196,6197,6198,6199
Bregenz 6201,6202,6203
Try this:
DECLARE #Table TABLE(ID INT, Description VARCHAR(25))
INSERT INTO #Table
VALUES (6192,'Salzburg' )
,(6193,'Salzburg' )
,(6194,'Salzburg' )
,(6196,'Innsbruck')
,(6197,'Innsbruck')
,(6198,'Innsbruck')
,(6199,'Innsbruck')
,(6201,'Bregenz' )
,(6202,'Bregenz' )
,(6203,'Bregenz' )
Query:
SELECT DISTINCT T2.Description,
SUBSTRING(
(
SELECT ','+CAST(T1.ID AS VARCHAR) AS [text()]
FROM #Table T1
WHERE T1.Description = T2.Description
ORDER BY T1.Description
FOR XML PATH ('')
), 2, 1000) [Ids]
FROM #Table T2
Result:
Description Ids
Bregenz 6201,6202,6203
Innsbruck 6196,6197,6198,6199
Salzburg 6192,6193,6194

Delete the same reference multiple columns rows safely?

I need to find all the parent-children relationships, which are all linked to my primary column ID
How I can delete the same reference columns in the table? Let say for example,if I want to delete "Google", I have to delete "HP" and Intel first also the child of HP as well.
I have tried the below thus far, but that works with only one column.
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE parent1 IS NULL
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = e.parent1
)
SELECT Name, Level, Path, Parent FROM tb
is this use full?
declare #tmp table (id int, Name varchar(10),Parent1 int,Parent2 int,Parent3 int,Parent4 int,Parent5 int)
insert into #tmp
SELECT 1,'Microsoft',NULL,NULL,NULL,NULL,NULL
union
SELECT 2,'Google',1,NULL,NULL,NULL,NULL
union
SELECT 3,'HP',NULL,2,NULL,NULL,NULL
union
SELECT 4,'Amazone',NULL,NULL,3,NULL,NULL
union
SELECT 5,'FB',NULL,NULL,NULL,4,NULL
union
SELECT 6,'Yahoo',NULL,NULL,NULL,4,NULL
union
SELECT 7,'Intel',NULL,NULL,2,NULL,NULL
union
SELECT 8,'Apple',7,5,NULL,NULL,NULL
select * from #tmp
;with name_tree as (
select *
from #tmp
where id = 2
union all
select c.*
from #tmp c
join name_tree p on (p.id = c.parent1 or p.id = c.parent2 or p.id = c.parent3 or p.id = c.parent4 or p.id = c.parent5)
)
delete from t
from #tmp t
JOIN name_tree c on t.id=c.id
select * from #tmp
I haven't provided an actual solution to your issue here. But I would recommend investigating recursive Common Table Expressions. That should allow you to find all the parent records, then you can run a delete on them.
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
You can simply modify the recursive CTE's where clause like in below query to get all rows that need to be deleted.
See live demo
create table krishtest (id int, name varchar(100), parent1 int, parent2 int)
insert into krishtest values
(1,'Microsoft', NULL, NULL),
(2,'Google',1,NULL),
(3,'HP',NULL,2),
(4,'amazon',3,NULL),
(5,'FB',NULL,4),
(6,'yahoo',3,NULL),
(7,'cisco',6,NULL)
;
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE -- COALESCE(parent1,parent2) IS NULL
name ='HP'
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = COALESCE(e.parent1,e.parent2)
)
--delete FROM krishtest where id in( select id from tb)
--select * from krishtest
SELECT Name, Level, Path, Parent FROM tb

Aggregate multiple rows into new single long row of data

I have a database with many rows of data per "case". Each "case" has a unique ID, but each row has a "multiple-choice-element" and a "value". Obviously there is a new row every time the user selects one of the multiple choice elements(mce) and the new value too. The unique ID is like a linchpin holding all of the rows together as a common element for this instance
The data is as follows:
UniqueID Value Text Username Contact
--------------------------------------------------
123456 No Sound Horn Johnson 0788
123456 Broken Headlight Johnson 0788
123456 Broken Windscreen Johnson 0788
I am looking to keep just one row of data, their user details, the key (unique ID), and then have multiple columns for each mce and each value.
UniqueID Username Contact Text Value Text Value Text Value
---------------------------------------------------------------------------------
123456 Johnson 0788 Horn No Sound Headlight Broken Windscreen Broken
I have done this using an update statement for each mce based on the Unique ID so far, but it's a bit clunky and long winded as a stored-procedure and can take quite a bit of time to run.
Can anyone suggest a better way please.
Thank you.
EDIT 2: I wasn't even sure this was possible, but I think I've found a way. Unfortunately, it's extremely hacky, and since you never posted your RDBMS tag, I don't even know if it will work for you.
I don't particularly like the solution, as I said - it feels clumsy to me, but it does work and I don't have any more time to dedicate to this issue. Unless someone else can figure out a way to make it work more efficiently, this is the best I can do for you. Note that I create and reference a temp table called #Numbers - you'll need this to stay in your query. Also note that you might end up with more columns than you want, but that will have to stay.
The following script will allow you to pivot two columns, preserving the relationship between a distinct value in column #1 (Txt) and multiple values in column #2 (Value)
CREATE TABLE #Data (UniqueID INT, Value varchar(10), Txt varchar(10), Username varchar(10), Contact INT)
INSERT INTO #Data (UniqueID, Value, Txt, Username, Contact)
SELECT 123456, 'No Sound', 'Horn', 'Johnson', 0788 UNION
SELECT 123456, 'Broken', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Smashed', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Shattered', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Busted', 'Headlight', 'Johnson', 0788 UNION
SELECT 123456, 'Inop', 'Brake', 'Johnson', 0788
DECLARE #sql AS varchar(max)
DECLARE #vpivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #vselect_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #tpivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #tselect_list AS varchar(max) -- Leave NULL for COALESCE technique
CREATE TABLE #Numbers (Number INT)
;WITH NumberSequence( Number ) AS
(
SELECT 1 as Number
UNION ALL
SELECT Number + 1
FROM NumberSequence
WHERE Number < 100
)
INSERT INTO #Numbers (Number)
SELECT Number FROM NumberSequence
SELECT
#vpivot_list = COALESCE(#vpivot_list + ', ', '') + '[' + TxtValCombinations + ']'
FROM
(
SELECT
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
) PossibleValues
SELECT
#tpivot_list = COALESCE(#tpivot_list + ', ', '') + '[' + TxtCombinations + ']'
FROM
(
SELECT
'T' + CAST(Txt.Number AS VARCHAR(5)) AS TxtCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
) PossibleValues
SELECT #vselect_list = STUFF(
(
SELECT',' +
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) --AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
ORDER BY Txt.Number, Val.Number
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, '')
SELECT #tselect_list = STUFF(
(
SELECT TxtValCombinations
FROM
(
SELECT',MAX(' +
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) + ') AS '+
'T' + CAST(Txt.Number AS VARCHAR(5)) + '_' +
'V' + CAST(Val.Number AS VARCHAR(5)) AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
INNER JOIN
#Numbers Val ON
Val.Number <
(
SELECT MAX(CountValue) MaxCountValue
FROM
(
SELECT COUNT(Value) CountValue
FROM #Data
GROUP BY UniqueID, Txt
) cv
)
UNION ALL
SELECT',MAX(' +
'T' + CAST(Txt.Number AS VARCHAR(5)) +') AS T' + CAST(Txt.Number AS VARCHAR(5)) --AS TxtValCombinations
FROM
#Numbers Txt
INNER JOIN
(
SELECT MAX(CountTxt) MaxCountTxt
FROM
(
SELECT COUNT(DISTINCT Txt) CountTxt
FROM #Data
GROUP BY UniqueID
) cv
) MaxCountTxt ON
Txt.Number <= MaxCountTxt.MaxCountTxt
) s
ORDER BY TxtValCombinations
FOR XML PATH(''), type
).value('.', 'varchar(max)'), 1, 1, '')
SET #sql = '
SELECT UniqueID, Username, Contact, ' + #tselect_list + '
FROM
(
SELECT UniqueID, Username, Contact, Txt, tPIVOT_CODE, ' + #vselect_list + '
FROM
(
SELECT b.UniqueID, Value, Username, Contact, Txt, tPIVOT_CODE, vPIVOT_CODE
FROM
(
SELECT
Data.UniqueID,
Data.Username,
Data.Contact,
Data.Txt,
''T'' + CAST(grpTxt.TxtNum AS VARCHAR(5)) AS tPIVOT_CODE,
Data.Value,
''T'' + CAST(grpTxt.TxtNum AS VARCHAR(5)) + ''_V'' + CAST(ROW_NUMBER() OVER (PARTITION BY Data.UniqueID, Data.Txt ORDER BY Data.Value) AS VARCHAR(4)) vPIVOT_CODE
FROM
#Data Data
INNER JOIN
(
SELECT UniqueID, Txt, ROW_NUMBER() OVER (PARTITION BY UniqueID ORDER BY Txt) AS TxtNum
FROM #Data
GROUP BY UniqueID, Txt
) grpTxt ON
Data.UniqueID = grpTxt.UniqueID AND
Data.Txt = grpTxt.Txt
) b
) vp
PIVOT (
MIN(Value)
FOR vPIVOT_CODE IN (
' + #vpivot_list + '
)
) AS vpvt
) tp
PIVOT (
MIN(Txt)
FOR tPIVOT_CODE IN (
' + #tpivot_list + '
)
) AS tpvt
GROUP BY UniqueID, UserName, Contact
ORDER BY UniqueID, UserName, Contact
'
--PRINT #sql
--PRINT #tselect_list
--PRINT #vpivot_list
EXEC (#sql)
DROP TABLE #Data
DROP TABLE #Numbers

Return Distinct Rows That Contain The Same Value/Character In SQL

I have a bit of a tricky situation. I have a column that contains a pipe delimited set of numbers in numerous rows in a table. For example:
Courses
-------------------
1|2
1|2|3
1|2|8
10
11
11|12
What I want to achieve is to return rows where the number only appears once in my output.
Ideally, I want to try and carry this out using SQL rather than having to carry out checks at a web application level. Carrying out a DISTINCT does not achieve what I want.
The desired output would be:
Courses
-------------------
1
2
3
8
10
11
12
I would appreciated if anyone can guide me in the right direction.
Thanks.
Please try:
declare #tbl as table(Courses nvarchar(max))
insert into #tbl values
('1|2'),
('1|2|3'),
('1|2|8'),
('10'),
('11'),
('11|12')
select * from #tbl
SELECT
DISTINCT CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS CVS
FROM
(
SELECT CAST ('<M>' + REPLACE(Courses, '|', '</M><M>') + '</M>' AS XML) AS CVS
FROM #tbl
) AS A CROSS APPLY CVS.nodes ('/M') AS Split(a)
ORDER BY 1
Try this one -
SET NOCOUNT ON;
DECLARE #temp TABLE
(
string VARCHAR(500)
)
DECLARE #Separator CHAR(1)
SELECT #Separator = '|'
INSERT INTO #temp (string)
VALUES
('1|2'),
('1|2|3'),
('1|2|8'),
('10'),
('11'),
('11|12')
-- 1. XML
SELECT p.value('(./s)[1]', 'VARCHAR(500)')
FROM (
SELECT field = CAST('<r><s>' + REPLACE(t.string, #Separator, '</s></r><r><s>') + '</s></r>' AS XML)
FROM #temp t
) d
CROSS APPLY field.nodes('/r') t(p)
-- 2. CTE
;WITH a AS
(
SELECT
start_pos = 1
, end_pos = CHARINDEX(#Separator, t.string)
, t.string
FROM #temp t
UNION ALL
SELECT
end_pos + 1
, CHARINDEX(#Separator, string, end_pos + 1)
, string
FROM a
WHERE end_pos > 0
)
SELECT d.name
FROM (
SELECT
name = SUBSTRING(
string
, start_pos
, ABS(end_pos - start_pos)
)
FROM a
) d
WHERE d.name != ''
Try this :
create table course (courses varchar(100))
insert into course values('1|2')
insert into course values('1|2|3')
insert into course values('1|2|8')
insert into course values('10')
insert into course values('11')
insert into course values('11|12')
Declare #col varchar(200)
SELECT
#col=(
SELECT DISTINCT c.courses + '|'
FROM course c
FOR XML PATH('')
);
select * from course
;with demo as(
select cast(substring(#col,1,charindex('|',#col,1)-1) AS INT) cou,charindex('|',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex('|',#col,pos+1)-pos-1)AS INT) cou,charindex('|',#col,pos+1) pos
from demo where pos<LEN(#col))
select distinct cou from demo
Could not manage without recursion :( Something like this could do the trich?
WITH splitNum(num, r)
AS
(
SELECT
SUBSTRING(<field>,1, CHARINDEX('|', <field>)-1) num,
SUBSTRING(<field>,CHARINDEX('|', <field>)+1, len(<field>)) r
FROM <yourtable> as a
UNION ALL
SELECT
SUBSTRING(r,1, CHARINDEX('|', r)-1) num,
SUBSTRING(r,CHARINDEX('|', r)+1, len(r)) r
FROM <yourtable> b
WHERE CHARINDEX('|', r) > 0
inner join splitNum as c on <whatevertheprimarykeyis>
)
SELECT distinct num FROM splitNum
Didn't make it run, but it should do the trick, just replace the and with the correct info
One way would be to use a recursive CTE:
with cte as
(select cast(case charindex('|',courses) when 0 then courses
else left(courses,charindex('|',courses)-1) end as int) course,
case charindex('|',courses) when 0 then ''
else right(courses,len(courses)-charindex('|',courses)) end courses
from courses
union all
select cast(case charindex('|',courses) when 0 then courses
else left(courses,charindex('|',courses)-1) end as int) course,
case charindex('|',courses) when 0 then ''
else right(courses,len(courses)-charindex('|',courses)) end courses
from cte
where len(courses)>0)
select distinct course from cte
SQLFiddle here.