Splitting single row into more columns based on column value - sql

I've a requirement to get 3 similar set of row data replacing the column value if any certain value exists in the given column('[#]' in this case). For example
---------------------
Type Value
---------------------
1 Apple[#]
2 Orange
3 Peach[#]
I need to modify the query to get value as below
----------------------
Type Value
--------------------
1 Apple1
1 Apple2
1 Apple3
2 Orange
3 Peach1
3 Peach2
3 Peach3
I could not come up with logic how to get this

You can also get the same result without recursivity :
select Type, Value from MyTable where Right(Value, 3) <> '[#]'
union
select Type, Replace(Value, '[#]', '1') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '2') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '3') from MyTable where Right(Value, 3) = '[#]'
order by 1, 2

Assuming there is only one digit (as in your example), then I would go for:
with cte as (
select (case when value like '%\[%%' then left(right(value, 2), 1) + 0
else 1
end) as cnt, 1 as n,
left(value, charindex('[', value + '[')) as base, type
from t
union all
select cnt, n + 1, base, type
from cte
where n + 1 <= cnt
)
select type,
(case when cnt = 1 then base else concat(base, n) end) as value
from cte;
Of course, the CTE can be easily extended to any number of digits:
(case when value like '%\[%%'
then stuff(left(value, charindex(']')), 1, charindex(value, '['), '') + 0
else 1
end)
And once you have the number, you can use another source of numbers. But the recursive CTE seems like the simplest solution for the particular problem in the question.

Try this query
DECLARE #SampleData AS TABLE
(
Type int,
Value varchar(100)
)
INSERT INTO #SampleData
VALUES (1, 'Apple[#]'), (2, 'Orange'), (3, 'Peach[#]')
SELECT sd.Type, cr.Value
FROM #SampleData sd
CROSS APPLY
(
SELECT TOP (IIF(Charindex('[#]', sd.Value) > 0, 3, 1))
x.[Value] + Cast(v.t as nvarchar(5)) as Value
FROM
(SELECT Replace(sd.Value, '[#]', '') AS Value) x
Cross JOIN (VALUES (1),(2),(3)) v(t)
Order by v.t asc
) cr
Demo link: Rextester

Using a recursive CTE
CREATE TABLE #test
(
Type int,
Value varchar(50)
)
INSERT INTO #test VALUES
(1, 'Apple[#]'),
(2, 'Orange'),
(3, 'Peach[#]');
WITH CTE AS (
SELECT
Type,
IIF(RIGHT(Value, 3) = '[#]', LEFT(Value, LEN(Value) - 3), Value) AS 'Value',
IIF(RIGHT(Value, 3) = '[#]', 1, NULL) AS 'Counter'
FROM
#test
UNION ALL
SELECT
B.Type,
LEFT(B.Value, LEN(B.Value) - 3) AS 'Value',
Counter + 1
FROM
#test AS B
JOIN CTE
ON B.Type = CTE.Type
WHERE
RIGHT(B.Value, 3) = '[#]'
AND Counter < 3
)
SELECT
Type,
CONCAT(Value, Counter) AS 'Value'
FROM
CTE
ORDER BY
Type,
Value
DROP TABLE #test

Related

SQL get average of a list in sql select

We have this column in the table named "pricehistory"
1634913730;48.38,1634916509;48.38,1635162352;37.96,1635177904;49.14,1635337722;1219.98,1635340811;27.17
that is an example data.
first is the timestamp than after ; is the price at this timestamp
But i want the average price from every timestamp in a select... is that possible?
I dont find any similiar examples somewhere and my tries to select doesnt work... i am not so good with sql
so i want average of all prices behind that ; and before ,
The , split the timestamp and prices
Some test data :
create table test ( id int not null, pricehistory text not null );
insert into test values ( 1, '1634913730;48.38,1634916509;48.38,1635162352;37.96,1635177904;49.14,1635337722;1219.98,1635340811;27.17' );
insert into test values ( 2, '1634913731;42.42,1634916609;21.21' );
If your RDBMS has some splitting function
Then it's quite easy, just split and use AVG. Here is an example using PostgreSQL :
SELECT id, AVG(SUBSTRING(v, 12, 42)::decimal) AS average
FROM test
INNER JOIN LATERAL regexp_split_to_table(pricehistory, E',') t(v) ON TRUE
GROUP BY id;
Then you get:
id | average
----+----------------------
2 | 31.8150000000000000
1 | 238.5016666666666667
(2 rows)
Otherwise
You can use a CTE to split the values manually. This is a bit more involved. Here is an example using PostgreSQL again :
WITH RECURSIVE T AS (
SELECT id,
-- We get the last value ...
SUBSTRING(pricehistory, LENGTH(pricehistory) - STRPOS(REVERSE(pricehistory), ',') + 2) AS oneprice,
pricehistory AS remaining
FROM test
UNION ALL
-- ... as we get the other values from the recursive CTE.
SELECT id,
LEFT(remaining, STRPOS(remaining, ',') - 1),
SUBSTRING(remaining, STRPOS(remaining, ',') + 1)
FROM T
WHERE STRPOS(remaining, ',') > 0
)
SELECT id, AVG(SUBSTRING(oneprice, 12)::decimal) AS average
FROM T
GROUP BY id;
Then you get:
id | average
----+----------------------
2 | 31.8150000000000000
1 | 238.5016666666666667
(2 rows)
MySql >= 8.0
I used Recursive Common Table Expressions (cte) to split pricehistory string by ','. Then I split price from timestamp by ';', cast price as decimal(10,2) and group by id to get average price by id.
WITH RECURSIVE
cte AS (SELECT id,
SUBSTRING_INDEX(pricehistory, ',', 1) AS price,
CASE WHEN POSITION(',' IN pricehistory) > 0
THEN SUBSTR(pricehistory, POSITION(',' IN pricehistory) + 1)
ELSE NULL END AS rest
FROM t
UNION ALL
SELECT id,
SUBSTRING_INDEX(rest, ',', 1) AS price,
CASE WHEN POSITION(',' IN rest) > 0
THEN SUBSTR(rest, POSITION(',' IN rest) + 1)
ELSE NULL END AS rest
FROM cte
WHERE rest IS NOT NULL)
SELECT id, AVG(CAST(SUBSTR(price, POSITION(';' IN price) + 1) AS decimal(10,2))) AS price_average
FROM cte
GROUP BY id;
A similar way to do the same (using regular expressions functions):
WITH RECURSIVE
cte AS (SELECT Id, concat(pricehistory, ',') AS pricehistory FROM t),
unnest AS (SELECT id,
pricehistory,
1 AS i,
REGEXP_SUBSTR(pricehistory, ';[0-9.]*,', 1, 1) AS price
FROM cte
UNION ALL
SELECT id,
pricehistory,
i + 1,
REGEXP_SUBSTR(pricehistory, ';[0-9.]*,', 1, i + 1)
FROM unnest
WHERE REGEXP_SUBSTR(pricehistory, ';[0-9.]*,', 1, i + 1) IS NOT NULL)
SELECT id, AVG(CAST(SUBSTR(price, 2, LENGTH(price) - 2) AS decimal(10,2))) AS price_average
FROM unnest
GROUP BY id;
you don't write what DBMS you are using.
In MS SQL-SERVER you can write something like this.
Create a function to convert string to multiple rows, and then use that in the query.
CREATE or ALTER FUNCTION dbo.BreakStringIntoRows (#CommadelimitedString varchar(1000), #Separator VARCHAR(1))
RETURNS #Result TABLE (Column1 VARCHAR(max))
AS
BEGIN
DECLARE #IntLocation INT
WHILE (CHARINDEX(#Separator, #CommadelimitedString, 0) > 0)
BEGIN
SET #IntLocation = CHARINDEX(#Separator, #CommadelimitedString, 0)
INSERT INTO #Result (Column1)
--LTRIM and RTRIM to ensure blank spaces are removed
SELECT RTRIM(LTRIM(SUBSTRING(#CommadelimitedString, 0, #IntLocation)))
SET #CommadelimitedString = STUFF(#CommadelimitedString, 1, #IntLocation, '')
END
INSERT INTO #Result (Column1)
SELECT RTRIM(LTRIM(#CommadelimitedString))--LTRIM and RTRIM to ensure blank spaces are removed
RETURN
END
create table test1 ( id int not null, pricehistory varchar(max) not null );
insert into test1 values ( 1, '1634913730;48.38,1634916509;48.38,1635162352;37.96,1635177904;49.14,1635337722;1219.98,1635340811;27.17' );
insert into test1 values ( 2, '1634913731;42.42,1634916609;21.21' );
Select *,
(
Select avg(CAST(RTRIM(LTRIM(SUBSTRING(column1, 0, CHARINDEX(';', column1, 0)))) as decimal)) From dbo.BreakStringIntoRows(pricehistory, ',')
) as AVG
FRom test1
sample output:

SQL Transform a list of numbers into ranges in one column

If I have a list of ranges in a table e.g.
ID Number
1 4
1 5
1 6
1 7
1 9
Is there a way to put this into the format: '4-7,9' into one varchar column using SQL ?
Thanks.
You can use ROW_NUMBER and XML PATH:
DECLARE #Mock TABLE (Id INT, Number INT)
INSERT INTO #Mock
VALUES
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(1, 9)
;WITH CTE
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Number) AS RowId,*
FROM #Mock
)
SELECT
STUFF(
(
SELECT
',' + CAST(MIN(C.Number) AS VARCHAR(10)) + CASE WHEN MIN(C.Number) = MAX(C.Number) THEN '' ELSE '-' + CAST(MAX(C.Number) AS VARCHAR(10)) END
FROM
CTE C
GROUP BY
C.Number - C.RowId
FOR XML PATH ('')
), 1, 1, '') Result
Output: 4-7,9
Considering you have another to find the order of ranges
;WITH cte
AS (SELECT *,
Sum(CASE
WHEN number = prev_lag + 1 THEN 0
ELSE 1
END)
OVER(
ORDER BY iden_col) AS grp
FROM (SELECT *,
Lag(number)
OVER(
partition BY [ID]
ORDER BY iden_col) AS prev_lag
FROM Yourtable)a),
intr
AS (SELECT id,
CASE
WHEN Min(number) = Max(number) THEN Cast(Min(number) AS VARCHAR(50))
ELSE Concat(Min(number), '-', Max(number))
END AS intr_res
FROM cte
GROUP BY id,
grp)
SELECT DISTINCT Id,
Stuff(concat_col, 1, 1, '')
FROM intr a
CROSS apply (SELECT ',' + intr_res
FROM intr b
WHERE a.ID = b.ID
FOR xml path('')) cs (concat_col)
Demo

Check anagrams using sql server

ACT and CAT are anagrams
I have to Write a function in sql server that takes 2 strings and given a Boolean output that indicates whether the both of them are anagram or not.
This doesnt make sense to do it in sql server,but,it is for learning purpose only
SQL Server is not good at this kind of things, but here you are:
WITH Src AS
(
SELECT * FROM (VALUES
('CAT', 'ACT'),
('CAR', 'RAC'),
('BUZ', 'BUS'),
('FUZZY', 'MUZZY'),
('PACK', 'PACKS'),
('AA', 'AA'),
('ABCDEFG', 'GFEDCBA')) T(W1, W2)
), Numbered AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) Num
FROM Src
), Splitted AS
(
SELECT Num, W1 Word1, W2 Word2, LEFT(W1, 1) L1, LEFT(W2, 1) L2, SUBSTRING(W1, 2, LEN(W1)) W1, SUBSTRING(W2, 2, LEN(W2)) W2
FROM Numbered
UNION ALL
SELECT Num, Word1, Word2, LEFT(W1, 1) L1, LEFT(W2, 1) L2, SUBSTRING(W1, 2, LEN(W1)) W1, SUBSTRING(W2, 2, LEN(W2)) W2
FROM Splitted
WHERE LEN(W1)>0 AND LEN(W2)>0
), SplitOrdered AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Num ORDER BY L1) LNum1,
ROW_NUMBER() OVER (PARTITION BY Num ORDER BY L2) LNum2
FROM Splitted
)
SELECT S1.Num, S1.Word1, S1.Word2, CASE WHEN COUNT(*)=LEN(S1.Word1) AND COUNT(*)=LEN(S1.Word2) THEN 1 ELSE 0 END Test
FROM SplitOrdered S1
JOIN SplitOrdered S2 ON S1.L1=S2.L2 AND S1.Num=S2.Num AND S1.LNum1=S2.LNum2
GROUP BY S1.Num, S1.Word1, S1.Word2
And results:
1 CAT ACT 1
2 CAR RAC 1
3 BUZ BUS 0
4 FUZZY MUZZY 0
5 PACK PACKS 0
6 AA AA 1
7 ABCDEFG GFEDCBA 1
First split (T-SQL Split Word into characters) both words into temporary tables. Then perform an outer join and check for nulls.
Edit thanks to George's comment:
split (T-SQL Split Word into characters) both words into temporary tables
Modify temporary tables or use CTEs to add a column with count(*) with group by letters clause
Perform a full outer join on two temporary tables using a letter and it's count in join condition
Check for nulls in the output - if there are none, you have an anagram
The first get in my mind:
DECLARE #word1 nvarchar(max) = NULL,
#word2 nvarchar(max) = 'Test 1',
#i int = 0, #n int
DECLARE #table TABLE (
id int,
letter int
)
SELECT #word1 = ISNULL(LOWER(#word1),''), #word2 = ISNULL(LOWER(#word2),'')
SELECT #n = CASE WHEN LEN(#word1) > LEN(#word2) THEN LEN(#word1) ELSE LEN(#word2) END
WHILE #n > 0
BEGIN
INSERT INTO #table
SELECT 1, ASCII(SUBSTRING(#word1,#n,1))
UNION ALL
SELECT 2, ASCII(SUBSTRING(#word2,#n,1))
SET #n=#n-1
END
SELECT CASE WHEN COUNT(*) = 0 THEN 1 ELSE 0 END isAnagram
FROM (
SELECT id, letter, COUNT(letter) as c
FROM #table
WHERE id = 1
GROUP BY id, letter)as t
FULL OUTER JOIN (
SELECT id, letter, COUNT(letter) as c
FROM #table
WHERE id = 2
GROUP BY id, letter) as p
ON t.letter = p.letter and t.c =p.c
WHERE t.letter is NULL OR p.letter is null
Output:
isAnagram
0
You can also use loops in functions, and they can work fast. I am not able to get any of the of other answers even close to the performance of this function:
CREATE FUNCTION IsAnagram
(
#value1 VARCHAR(255)
, #value2 VARCHAR(255)
)
RETURNS BIT
BEGIN
IF(LEN(#value1) != LEN(#value2))
RETURN 0;
DECLARE #firstChar VARCHAR(3);
WHILE (LEN(#value1) > 0)
BEGIN
SET #firstChar = CONCAT('%', LEFT(#value1, 1), '%');
IF(PATINDEX(#firstChar, #value2) > 0)
SET #value2 = STUFF(#value2, PATINDEX(#firstChar, #value2), 1, '');
ELSE
RETURN 0;
SET #value1 = STUFF(#value1, 1, 1, '');
END
RETURN (SELECT IIF(#value2 = '', 1, 0));
END
GO
SELECT dbo.IsAnagram('asd', 'asd')
--1
SELECT dbo.IsAnagram('asd', 'dsa')
--1
SELECT dbo.IsAnagram('assd', 'dsa')
--0
SELECT dbo.IsAnagram('asd', 'dssa')
--0
SELECT dbo.IsAnagram('asd', 'asd')
This is something a numbers table can help with.
Code to create and populate a small numbers table is below.
CREATE TABLE dbo.Numbers
(
Number INT PRIMARY KEY
);
WITH Ten(N) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
INSERT INTO dbo.Numbers
SELECT ROW_NUMBER() OVER (ORDER BY ##SPID) AS Number
FROM Ten T10,
Ten T100,
Ten T1000
Once that is in place you can use
SELECT W1,
W2,
IsAnagram = CASE
WHEN LEN(W1) <> LEN(W2)
THEN 0
ELSE
CASE
WHEN EXISTS (SELECT SUBSTRING(W1, Number, 1),
COUNT(*)
FROM dbo.Numbers
WHERE Number <= LEN(W1)
GROUP BY SUBSTRING(W1, Number, 1)
EXCEPT
SELECT SUBSTRING(W2, Number, 1),
COUNT(*)
FROM dbo.Numbers
WHERE Number <= LEN(W2)
GROUP BY SUBSTRING(W2, Number, 1))
THEN 0
ELSE 1
END
END
FROM (VALUES
('CAT', 'ACT'),
('CAR', 'RAC'),
('BUZ', 'BUS'),
('FUZZY', 'MUZZY'),
('PACK', 'PACKS'),
('AA', 'AA'),
('ABCDEFG', 'GFEDCBA')) T(W1, W2)
Or an alternative implementation could be
IsAnagram = CASE
WHEN LEN(W1) <> LEN(W2)
THEN 0
ELSE
CASE
WHEN EXISTS (SELECT 1
FROM dbo.Numbers N
CROSS APPLY (VALUES(1,W1),
(2,W2)) V(Col, String)
WHERE N.Number <= LEN(W1)
GROUP BY SUBSTRING(String, Number, 1)
HAVING COUNT(CASE WHEN Col = 1 THEN 1 END) <>
COUNT(CASE WHEN Col = 2 THEN 1 END))
THEN 0
ELSE 1
END
END

How to compare string type columns character by character without looping?

I have two columns: One column holds actual answers, the other one is the answer_key.
I want to compare answers to answer_key and have scores in the third column:
ID Answers Answer_key Score
1 ABCD ABCC 1110
2 ACD DCA 010
Of course, I can check the length, loop through each character to compare them individually, and get the score.
However, is there an alternative? Possibly based on XML path?
You might try binary values rather than letters.
A=0001 B=0010 C=0100 D=1000
ABCD = 0001001001001000 (0x1248)
ABCC = 0001001001000100 (0x1244)
Score = (Answers XOR Answer_key) XOR 11111111
The XOR 11111111 is optional
What you want to do is to split each char in Answers and Answers_Key into separate rows and then compare them. This can be done using a Recursive CTE. The concatenation is done using the FOR XML PATH function.
CREATE TABLE temp(
Answers VARCHAR(10),
Answer_Key VARCHAR(10)
)
INSERT INTO temp VALUES ('ABCD', 'ABCC'), ('ACD', 'DCA');
;WITH temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
),
cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answer_Key = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = 1
FROM temp_numbered t
UNION ALL
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, 1, 1),
Answers = STUFF(Answer_Key, 1, 1, ''),
Answers_Char = SUBSTRING(Answers, 1, 1),
Answers = STUFF(Answers, 1, 1, ''),
RowID = RowID + 1
FROM cte
WHERE LEN(Answer_Key) > 0
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
DROP TABLE temp
Here is another way using a Tally Table:
;WITH tally(N) AS(
SELECT TOP 11000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM sys.columns
)
,temp_numbered AS(
SELECT
ID = ROW_NUMBER() OVER(ORDER BY Answer_Key),
*
FROM temp
)
,cte AS(
SELECT
ID,
Answer_Key_Char = SUBSTRING(Answer_Key, N, 1),
Answers_Char = SUBSTRING(Answers, N, 1),
RowID = N
FROM temp_numbered tn
CROSS JOIN Tally t
WHERE t.N <= LEN(tn.Answer_Key)
)
SELECT
Answers,
Answer_Key,
Score = (SELECT
CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
FROM cte
WHERE ID = t.ID
ORDER BY ID, RowID
FOR XML PATH(''))
FROM temp_numbered t
I seems easiest to loop through each character for the whole set at once:
-- get max Answer length
declare #len int,#max_len int
select #max_len = max(len(Answers)),
#len = 1
from Answers
-- update scores
while #len <= #max_len
begin
update Answers
set Score = isnull(Score,'') + '1'
where substring(Answers,#len,1) = substring(Answer_Key,#len,1)
and len(Answers) >= #len
update Answers
set Score = isnull(Score,'') + '0'
where substring(Answers,#len,1) != substring(Answer_Key,#len,1)
and len(Answers) >= #len
set #len = #len + 1
end
-- return Scores
select * from Answers
SQL FIDDLE
Expanding on the answer from #weswesthemenace to get around cte limit.
DECLARE #Answers TABLE
(
Id INT IDENTITY(1, 1) not null,
Answers VARCHAR(MAX) not null,
Answer_Key VARCHAR(MAX) not null
)
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCD', 'ABCC')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ACD', 'DCA')
INSERT INTO #Answers (Answers, Answer_Key) VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEGGHIJKLMNOPQRSTUVXXYZABCDEFGHIIKKMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXZZ');
WITH
E01(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E01 a CROSS JOIN E01 b),
E04(N) AS (SELECT 1 FROM E02 a CROSS JOIN E02 b),
E08(N) AS (SELECT 1 FROM E04 a CROSS JOIN E04 b),
E16(N) AS (SELECT 1 FROM E08 a CROSS JOIN E08 b),
E32(N) AS (SELECT 1 FROM E16 a CROSS JOIN E16 b),
cteTally(N) AS (SELECT row_number() OVER (ORDER BY N) FROM E32)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE when SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) then '1' else '0' end
FROM #Answers a
CROSS APPLY cteTally n
WHERE b.Id = a.Id AND n.N <= DATALENGTH(b.Answers)
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b
this can be simplified by a utility Number function in the database. Mine is called dbo.Number(start, end)
SELECT b.Answers, b.Answer_Key,
(
SELECT CASE WHEN SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) THEN '1' ELSE '0' END
FROM #Answers a
CROSS APPLY dbo.Number(1, DATALENGTH(b.Answers)) n
WHERE b.Id = a.Id
ORDER BY ID, n.N
FOR XML PATH('')
) Score
FROM #Answers b

SQL insert data to other table after split string

I have a table WCA:
ID TYPE ..
1 *1*3*5*
2 *1*5*
..
Now i want move data to new table WCA_TYPE:
ID WCA_ID TYPE
1 1 1
2 1 3
3 1 5
4 2 1
5 2 5
..
ID here is auto increase.
How to write sql in MS SQL server to split type from old table to multi type & insert it into new table.
DECLARE #CurID INT, #MaxID INT, #t VARCHAR(200)
SELECT #CurID = 1, #MaxID = MAX(ID) FROM WCA
WHILE #CurID <= #MaxID
BEGIN
SELECT #t = TYPE
FROM WCA
WHERE ID = #CurID
;WITH Pieces([Pos], [start], [stop]) AS (
SELECT 1, 1, CHARINDEX('*', #t)
UNION ALL
SELECT [Pos] + 1, [stop] + 1, CHARINDEX('*', #t, [stop] + 1)
FROM Pieces
WHERE [stop] > 0
)
INSERT INTO WCA_TYPE(WCA_ID, TYPE)
SELECT #CurID, T.Value
FROM
( SELECT [Pos], SUBSTRING(#t, start, CASE WHEN [stop] > 0 THEN [stop]-[start] ELSE 4000 END) AS [Value]
FROM Pieces
) T
WHERE T.Value <> ''
SET #CurID = #CurID + 1
END
SELECT *
FROM WCA_TYPE
look this fiddle
Assuming the your type column always have 3 digits you can use this.
INSERT INTO wca_type (WCA_id,type)
SELECT 1,SUBSTRING(CONVERT(nvarchar(MAX),type),1,1) FROM wca
UNION ALL
SELECT 1,SUBSTRING(CONVERT(nvarchar(MAX),type),2,1) FROM wca
UNION ALL
SELECT 1,SUBSTRING(CONVERT(nvarchar(MAX),type),3,1) FROM wca
You could use a recursive cte - e.g.
CREATE TABLE #WCA_TYPE
(ID INT IDENTITY(1, 1) PRIMARY KEY
,WCA_ID INT
,TYPE INT);
WITH sampleData(WCA_ID, TYPE) AS
(
SELECT
*
FROM ( VALUES ('1', '1*3*5')
,('2', '1*5')
) nTab(nCol1, nCol2)
)
,rep(WCA_ID, item, delim) AS
(
SELECT
WCA_ID
,TYPE item
,'*' delim
FROM sampleData
UNION ALL
SELECT
WCA_ID
,LEFT(item, CHARINDEX(delim, item, 1) - 1) item
,delim
FROM rep
WHERE (CHARINDEX(delim, item, 1) > 0)
UNION ALL
SELECT
WCA_ID
,RIGHT(item, LEN(item) - CHARINDEX(delim, item, 1)) item
,delim
FROM rep
WHERE (CHARINDEX(delim, item, 1) > 0)
)
INSERT #WCA_TYPE
(TYPE
,WCA_ID)
SELECT
item
,WCA_ID
FROM rep
WHERE (CHARINDEX(delim, item, 1) = 0)
ORDER BY WCA_ID
OPTION (MAXRECURSION 0);
SELECT * FROM #WCA_TYPE;
WITH CTE AS
(
select id,Type,0 as startPos,
CHARINDEX('*',TYPE)-1 as endPos from WCA
UNION ALL
select id,Type,endPos+2 as startPos,
CHARINDEX('*',TYPE,endPos+2)-1 as endPos from CTE
where CHARINDEX('*',TYPE,endPos+2)>0
)
INSERT INTO WCA_TYPE (WCA_ID, TYPE)
select ID,
CASE WHEN EndPos>0
THEN
Substring(Type,StartPos,EndPos-StartPos+1)
else
Type
end as Type
from CTE
where EndPos<>0
SQLFiddle Select demo