Related
I have the following string:
1119/2/483/11021
I would like to reverse the order of the elements in that string. Desired output:
11021/483/2/1119
T-SQL Version 2014
You need an ordered split function, e.g. (inspiration):
CREATE FUNCTION dbo.SplitOrdered
(
#list nvarchar(max),
#delim nvarchar(10)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH w(n) AS (SELECT 0 FROM (VALUES (0),(0),(0),(0)) w(n)),
k(n) AS (SELECT 0 FROM w a, w b),
r(n) AS (SELECT 0 FROM k a, k b, k c, k d, k e, k f, k g, k h),
p(n) AS (SELECT TOP (COALESCE(LEN(#list), 0))
ROW_NUMBER() OVER (ORDER BY ##SPID) -1 FROM r),
spots(p) AS
(
SELECT n FROM p
WHERE (SUBSTRING(#list, n, LEN(#delim + 'x') - 1) LIKE #delim OR n = 0)
),
parts(p,val) AS
(
SELECT p, SUBSTRING(#list, p + LEN(#delim + 'x') - 1,
LEAD(p, 1, 2147483647) OVER (ORDER BY p) - p - LEN(#delim))
FROM spots AS s
)
SELECT listpos = ROW_NUMBER() OVER (ORDER BY p),
Item = LTRIM(RTRIM(val))
FROM parts
);
Then you can reassemble using STRING_AGG() (if SQL Server 2017 or better) or FOR XML PATH on lower versions:
SQL Server 2017 +
DECLARE #OriginalString nvarchar(255) = N'1119/2/483/11021';
SELECT NewString = STRING_AGG(o.Item, N'/')
WITHIN GROUP (ORDER BY listpos DESC)
FROM dbo.SplitOrdered(#OriginalString, N'/') AS o;
SQL Server < 2017
DECLARE #OriginalString nvarchar(255) = N'1119/2/483/11021';
SELECT NewString = STUFF(
(SELECT N'/' + o.Item
FROM dbo.SplitOrdered(#OriginalString, N'/') AS o
ORDER BY o.listpos DESC
FOR XML PATH(''), TYPE).value(N'./text()[1]', N'nvarchar(max)'),1,1,N'');
Example db<>fiddle
Please try the following solution based on the built-in PARSENAME() T-SQL function.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Tokens VARCHAR(MAX));
INSERT INTO #tbl (Tokens) VALUES
('1119/2/483/11021'),
('1120/25/484/1102');
-- DDL and sample data population, end
SELECT tbl.*
, PARSENAME(c, 1) + '/' +
PARSENAME(c, 2) + '/' +
PARSENAME(c, 3) + '/' +
PARSENAME(c, 4) AS Result
FROM #tbl AS tbl
CROSS APPLY (VALUES (REPLACE(Tokens, '/', '.') )) AS t(c);
Output
+----+------------------+------------------+
| ID | Tokens | Result |
+----+------------------+------------------+
| 1 | 1119/2/483/11021 | 11021/483/2/1119 |
| 2 | 1120/25/484/1102 | 1102/484/25/1120 |
+----+------------------+------------------+
First, split the string and convert it into a column then order by desc and display into multiple row values into a single row. In the following code, you can set any string and split char.
Try following way.
DECLARE #S varchar(max) ,
#Split char(1),
#X xml
DECLARE #Names VARCHAR(8000)
SELECT #S = '1119/2/483/11021',
#Split = '/'
SELECT #X = CONVERT(xml,' <root> <myvalue>' +
REPLACE(#S,#Split,'</myvalue> <myvalue>') + '</myvalue> </root> ')
select #Names = COALESCE(#Names + '/', '') + Value from (
select rowno,Value from (
select ROW_NUMBER() OVER(ORDER BY d) AS rowno , Value from (
SELECT T.c.value('.','varchar(20)') as Value,0 as d
FROM #X.nodes('/root/myvalue') T(c)
) m
) r
) t order by t.rowno desc
select #Names as ReverseString
Splitting the string into sub-strings, and then joining them back up, is most likely going to be a good approach.
Some comments mention using string-reverse, but that doesnt seem to be a good approach at all in your case, since you just want to reverse the order of words within the current string, not actually reverse the entire text-string character-by-character.
PS: string_split does not guarantee the order of the chunks!
In SQL I want to add 0 in front and , at the end of each character.
Example: A30F1 -> 0A,03,00,0F,01
I don't want to use cursor if possible.
Thanks!
EIDT:
I apologize for not asking the most appropriate question at the beginning.
In short, I have a table and for each value in the column name I have to convert it to the desired format. For example, we have a #Temp table:
CREATE TABLE #Temp (id INT, name VARCHAR(25))
INSERT INTO #Temp VALUES (1, 'A30F1'), (2, 'B51R9'), (3, 'L1721')
SELECT * FROM #Temp
One method would be to use a Tally to split the string into it's individual characters, and then use concatenation to add the 0 to the start, and STRING_AGG to comma delimit the results:
DECLARE #YourValue varchar(5) = 'A30F1';
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (LEN(#YourValue))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2) --Up to 100 characters, add more cross joins for more characters
SELECT STRING_AGG(CONCAT('0',SS.C),',') WITHIN GROUP (ORDER BY T.I) AS NewString
FROM (VALUES(#YourValue))V(YourValue)
CROSS JOIN Tally T
CROSS APPLY (VALUES(SUBSTRING(V.YourValue,T.I,1)))SS(C);
It appears this is meant to be against a table, not a single value. This needs, however, very few changes to work against a table:
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT MAX(LEN(YourColumn)) FROM dbo.YourTable)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2) --Up to 100 characters, add more cross joins for more characters
SELECT STRING_AGG(CONCAT('0',SS.C),',') WITHIN GROUP (ORDER BY T.I) AS NewString
FROM dbo.YourTable YT
JOIN Tally T ON LEN(YT.YourColumn) >= T.I
CROSS APPLY (VALUES(SUBSTRING(YT.YourColumn,T.I,1)))SS(C)
GROUP BY YT.YourColumn;
db<>fiddle
I solved the simplest possible with a few variables, WHILE and SUBSTRING
DECLARE #var VARCHAR(20) = 'A30F1', #i INT = 1, #res NVARCHAR(20)
WHILE (#i <= LEN(#var))
BEGIN
SET #res = #res + '0' + SUBSTRING(#var, #i, 1) + ','
SET #i = #i + 1
END
SELECT LEFT(#res, LEN(#res) - 1) output
Check demo on DB<>FIDDLE.
Original answer:
A recursive CTE and a STRING_AGG() call is also an option (SQL Server 2017+ is needed):
DECLARE #text varchar(max) = 'A30F1';
WITH rCTE AS
(
SELECT 1 AS CharacterPosition, SUBSTRING(#text, 1, 1) AS Character
UNION ALL
SELECT CharacterPosition + 1, SUBSTRING(#text, CharacterPosition + 1, 1)
FROM rCTE
WHERE CharacterPosition < LEN(#text)
)
SELECT STRING_AGG('0' + Character, ',') WITHIN GROUP (ORDER BY CharacterPosition)
FROM rCTE
OPTION (MAXRECURSION 0);
Update:
You need a different statement, if the names are stored in a table, again using recursion and STRING_AGG():
Table:
CREATE TABLE #Temp (id INT, name VARCHAR(25))
INSERT INTO #Temp VALUES (1, 'A30F1'), (2, 'B51R9'), (3, 'L1721')
Statement:
; WITH rCTE AS (
SELECT
t.id AS id,
LEFT(t.name, 1) AS Character,
STUFF(t.name, 1, 1, '') AS CharactersRemaining,
1 AS CharacterPosition
FROM #Temp t
UNION ALL
SELECT
r.id,
LEFT(r.CharactersRemaining, 1),
STUFF(r.CharactersRemaining, 1, 1, ''),
CharacterPosition + 1
FROM rCTE r
WHERE LEN(r.CharactersRemaining) > 0
)
SELECT
id,
STRING_AGG('0' + Character, ',') WITHIN GROUP (ORDER BY CharacterPosition) AS name
FROM rCTE
GROUP BY id
OPTION (MAXRECURSION 0);
Result:
id name
1 0A,03,00,0F,01
2 0B,05,01,0R,09
3 0L,01,07,02,01
If you are only applying this to English alphabet characters and digits as in your example you could do this.
CREATE TABLE #Temp (id INT, name VARCHAR(25))
INSERT INTO #Temp VALUES (1, 'A30F1'), (2, 'B51R9'), (3, 'L1721'), (4, 'A')
SELECT SUBSTRING(REPLACE(
0x00 + CAST(CAST(name AS NVARCHAR(25)) AS BINARY(50)), CHAR(0), '0,')
, 3
, LEN(name) * 3 - 1)
FROM #Temp
returns
0A,03,00,0F,01
0B,05,01,0R,09
0L,01,07,02,01
0A
This takes advantage of the fact that the binary representation of the nvarchar and varchar is the same for this limited character set except for padding out with 0x00
'A30F1' -> 0x4133304631
N'A30F1' -> 0x41003300300046003100
I have a column which has alphanumeric strings like 123x758v961j.
I need to split this alphanumeric number to chars an have blank space between values.
Example: 123x758v961j =====> 1 2 3 x 7 5 8 v 9 6 1 j
I need a function which returns this solution.
Using NGrams8K to split the string into individual characters and then the "classic" FOR XML PATH and STUFF solution to combine the characters back you can do this:
SELECT V.S,
STUFF((SELECT ' ' + NG.token
FROM dbo.NGrams8k(V.S,1) NG
ORDER BY NG.position
FOR XML PATH(''),TYPE).value('.','varchar(100)'),1,1,'') AS S2 --Use a varchar length that is double(-1) then length of your actual data type here
FROM (VALUES('123x758v961j'))V(S);
Just another way
CREATE FUNCTION dbo.SplitToChars(
#String NVARCHAR(300)
)
RETURNS NVARCHAR(300)
AS
BEGIN
DECLARE #Result NVARCHAR(300) = '';
WITH CTE AS
(
SELECT 1 N
UNION ALL
SELECT N + 1
FROM CTE
WHERE N < LEN(#String)
)
SELECT #Result = CONCAT(#Result, SUBSTRING(#String, N, 1), N' ')
FROM CTE;
RETURN (RTRIM(#Result));
END;
Then just SELECT dbo.SplitToChars(N'123x758v961j')
Returns:
1 2 3 x 7 5 8 v 9 6 1 j
Live Demo
And one more approach ;-)
DECLARE #str VARCHAR(100)='123x758v961j';
WITH Tally(Nmbr) AS
(
SELECT TOP(LEN(#str)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values
)
SELECT TRIM(
(
SELECT ' ' + SUBSTRING(#str,Nmbr,1)
FROM Tally
ORDER BY Nmbr
FOR XML PATH('')
)
);
The idea is, to use a tally-on-the-fly (a list of running numbers for each position within #str) to read the characters one-by-one. This derived table is reconcatenated.
And - just for fun - to demonstrate the range of approaches, one more using a quirky update (something to avoid actually ;-) )
WITH Tally(Nmbr) AS
(
SELECT TOP(LEN(#str)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values
)
SELECT #str=STUFF(#str,Nmbr,0,' ')
FROM Tally
ORDER BY Nmbr DESC;
SELECT #str;
I have a task to write a stored procedure or a function to return all possible combinations of a 4 digit number.
For example, if I pass 1234 to the stored procedure or function, it should return 4 digit numbers (all possible combinations), like
1123, 1112, 1324, 1342, 2134, 2234
and so on.
It can be of 4 digits only.
I have been doing this with using LIKE operator:
select *
from Table
where mynumber like '%1%'
and mynumber like '%2%'
and mynumber like '%3%'
and mynumber like '%4%'
but the problem is, I have hardcoded the numbers 1,2,3 and 4.
The number can be anything.
And these many LIKE operators can also impact the performance on a large table.
Can anybody give me some generic query to get the combinations?
Thanks in advance.
You can use a cross join:
with digits as (
select substring(num, 1, 1) as d union all
select substring(num, 2, 1) as d union all
select substring(num, 3, 1) as d union all
select substring(num, 4, 1) as d
)
select (d1.d + d2.d + d3.d + d4.d)
from digits d1 cross join
digits d2 cross join
digits d3 cross join
digits d4;
Note: This assumes that the number is a string (based on the fact that you use like in your question).
First you need to be able to break a four-digit number into separate digits. I suggest using a table variable and the modulus operator. Assuming we have an integer input named #input, we can break it into its digits using this:
DECLARE #Digits Table(Number int)
INSERT INTO #Digits(Number)
VALUES (#input % 10),
(#input / 10 % 10),
(#input / 100 % 10),
(#input / 1000 % 10)
Now we have a table with four rows, one row per digit.
To create a combination of four digits, we need to include the table four times, meaning we need three joins. The joins have to be set up so no digit is duplicated. Thus our FROM and JOIN clauses will look like this:
FROM #Digits D1
JOIN #Digits D2 ON D2.Number <> D1.Number
JOIN #Digits D3 ON D3.Number <> D1.Number
AND D3.Number <> D2.Number
JOIN #Digits D4 ON D4.Number <> D1.Number
AND D4.Number <> D2.Number
AND D4.Number <> D3.Number
Now to take the values and make a new, four-digit integer:
SELECT Number = D1.Number * 1000
+ D2.Number * 100
+ D3.Number * 10
+ D4.Number
The complete solution:
CREATE PROC Combine(#input AS int)
AS
BEGIN
DECLARE #Digits Table(Number int)
;
INSERT INTO #Digits(Number)
VALUES (#input % 10),
(#input / 10 % 10),
(#input / 100 % 10),
(#input / 1000 % 10)
;
SELECT Number = D1.Number * 1000
+ D2.Number * 100
+ D3.Number * 10
+ D4.Number
FROM #Digits D1
JOIN #Digits D2 ON D2.Number <> D1.Number
JOIN #Digits D3 ON D3.Number <> D1.Number
AND D3.Number <> D2.Number
JOIN #Digits D4 ON D4.Number <> D1.Number
AND D4.Number <> D2.Number
AND D4.Number <> D3.Number
ORDER BY Number
;
END
Usage:
EXEC Combine 1234
Resultset:
Number
------
1234
1243
1324
1342
1423
1432
2134
2143
2314
2341
2413
2431
3124
3142
3214
3241
3421
4123
4132
4213
4231
4312
4321
24 row(s)
Click here to run the above code on RexTester
Improving #GordonLinoff's answer, you can add an additional column in you CTE so that you can only make sure that each number is only used once:
declare #num varchar(max);
set #num = '1234';
with numCTE as (
select SUBSTRING(#num, 1,1) as col, 1 as cnt union
select SUBSTRING(#num, 2,1) as col, 3 as cnt union
select SUBSTRING(#num, 3,1) as col, 9 as cnt union
select SUBSTRING(#num, 4,1) as col, 27 as cnt
)
select DISTINCT (a1.col+a2.col+a3.col+a4.col)
from numCTE a1
cross join numCTE a2
cross join numCTE a3
cross join numCTE a4
where a1.cnt + a2.cnt + a3.cnt + a4.cnt = 40
Additionally, you can remove the WHERE to allow each number to be used more than once.
Don't forget the DISTINCT keyword. :)
You can try this.
select * from Table where mynumber like '%[1234][1234][1234][1234]%'
if it should only be 4 digit
select * from Table where mynumber like '[1234][1234][1234][1234]'
Also, you can use [1-4] instead of [1234]
Here's query to return all combinations of four digits (characters in general):
select A.col + B.col + C.col + D.col [Combinations] from
(values ('1'),('2'),('3'),('4')) as A(col) cross join
(values ('1'),('2'),('3'),('4')) as B(col) cross join
(values ('1'),('2'),('3'),('4')) as C(col) cross join
(values ('1'),('2'),('3'),('4')) as D(col)
Taking inspiration from this answer:
WITH n AS (
SELECT n FROM (VALUES (1), (2), (3), (4)) n (n)
) SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM n ones, n tens, n hundreds, n thousands
You can define a table in your stored procedure will all possible combinations but using letters for codding:
DECLARE #Combinations TABLE
(
[value] CHAR(4)
);
INSERT INTO #Combinations ([value])
VALUES ('AAAA')
,('AAAB')
,('AAAC')
,('AAAD')
...
Then update every latter with the input number:
DECLARE #Numner1 TINYINT = 2
,#Numner2 TINYINT = 5
,#Numner3 TINYINT = 1
,#Numner4 TINYINT = 3;
UPDATE #Combinations
SET [value] = REPLACE([value], 'A', #Numner1);
UPDATE #Combinations
SET [value] = REPLACE([value], 'B', #Numner2);
UPDATE #Combinations
SET [value] = REPLACE([value], 'C', #Numner3);
UPDATE #Combinations
SET [value] = REPLACE([value], 'D', #Numner4);
Then just join the table with your table:
select *
from Table A
INNER JOIN #Combinations B
ON A.[mynumber] = B.[value];
Try This approach
DECLARE #Num INT = 5432
;WITH CTE
AS
(
SELECT
SeqNo = 1,
Original = CAST(#Num AS VARCHAR(20)),
Num = SUBSTRING(CAST(#Num AS VARCHAR(20)),1,1)
UNION ALL
SELECT
SeqNo = SeqNo+1,
Original,
Num = SUBSTRING(Original,SeqNo+1,1)
FROM CTE
WHERE SeqNo < LEN(Original)
)
SELECT
MyStr = C1.Num+C2.Num+C3.Num+C4.Num
FROM CTE C1
CROSS JOIN CTE C2
CROSS JOIN CTE C3
CROSS JOIN CTE C4
WHERE
(
C1.SeqNo <> C2.SeqNo
AND
C3.SeqNo <> C4.SeqNo
AND
C4.SeqNo <> C1.SeqNo
AND
C2.SeqNo <> C3.SeqNo
AND
C1.SeqNo <> C3.SeqNo
AND
C4.SeqNo <> C2.SeqNo
)
ORDER BY 1
My Result
MyStr
2345
2354
2435
2453
2534
2543
3245
3254
3425
3452
3524
3542
4235
4253
4325
4352
4523
4532
5234
5243
5324
5342
5423
5432
Please try this. SET BASED Approach to generate all Possible combinations of a number-
IF OBJECT_ID('Tempdb..#T') IS NOT NULL
DROP TABLE tempdb..#T
DECLARE # AS INT = 1234
IF LEN(#) <= 7
BEGIN
DECLARE #str AS VARCHAR(100)
SET #str = CAST(# AS VARCHAR(100))
DECLARE #cols AS VARCHAR(100) = ''
SELECT DISTINCT SUBSTRING(#str,NUMBER,1) n INTO #T FROM MASTER..spt_values WHERE number > 0 AND number <= LEN(#)
SELECT #cols = #cols + r
FROM ( SELECT DISTINCT CONCAT(', o',number,'.n') r FROM MASTER..spt_values WHERE number > 0 AND number <= (LEN(#)-1) )q
DECLARE #ExecStr AS VARCHAR(1000) = ''
SET #ExecStr = 'SELECT CAST(CONCAT( a.n' + #cols + ' ) AS INT) Combinations FROM #T a'
SELECT #ExecStr = #ExecStr + r FROM
(
SELECT DISTINCT CONCAT(' CROSS APPLY ( SELECT * FROM #T b' , number , ' WHERE ( b' , number, '.n' , ' <> a.n ) ',
CASE WHEN number = 1 then ''
WHEN number = 2 then ' AND ( b2.n <> o1.n )'
WHEN number = 3 then ' AND ( b3.n <> o1.n ) AND ( b3.n <> o2.n ) '
WHEN number = 4 then ' AND ( b4.n <> o1.n ) AND ( b4.n <> o2.n ) AND ( b4.n <> o3.n ) '
WHEN number = 5 then ' AND ( b5.n <> o1.n ) AND ( b5.n <> o2.n ) AND ( b5.n <> o3.n ) AND ( b5.n <> o4.n ) '
WHEN number = 6 then ' AND ( b6.n <> o1.n ) AND ( b6.n <> o2.n ) AND ( b6.n <> o3.n ) AND ( b6.n <> o4.n ) AND ( b6.n <> o5.n ) '
END
,') o' , number ) r FROM
MASTER..spt_values
WHERE number > 0 AND number <= (LEN(#)-1)
)p
EXEC (#ExecStr)
END
IF OBJECT_ID('tempdb..#T') IS NOT NULL
DROP TABLE tempdb..#T
OUTPUT
1432
1342
1423
1243
1324
1234
2431
2341
2413
2143
2314
2134
3421
3241
3412
3142
3214
3124
4321
4231
4312
4132
4213
4123
from - https://msbiskills.com/2016/05/20/sql-puzzle-generate-possible-combinations-of-a-number-puzzle/
You can try following alternative SQL Script as well
declare #param varchar(4) = '1234'
;with combination as (
select
distinct rn = DENSE_RANK() over (Order By num), num
from (
select substring(#param,1,1) as num
union all
select substring(#param,2,1)
union all
select substring(#param,3,1)
union all
select substring(#param,4,1)
) t
)
select
c1.num, c2.num, c3.num, c4.num,
cast(c1.num as char(1)) + cast(c2.num as char(1)) + cast(c3.num as char(1)) + cast(c4.num as char(1)) as number
from combination c1, combination c2, combination c3, combination c4
It produces 256 numbers for 4 digits
Actually this code is from SQL code which returns non-repeatable combinations in SQL of given set of items, but modified it to enable repeats of items in the output
I need to split a string in a column into one character each into it's own column in SQL Server 2012.
Example: if I have a column with 'ABCDE', I need to split it into 'A', 'B', 'C', 'D', 'E', with each of these into their own columns.
The length of the column to be split may vary, so I need this to be as dynamic as possible.
My question is different from the other post (Can Mysql Split a column?) since mine doesn't have any delimiters.
Thanks
You can do this like this:
DECLARE #t TABLE(id int, n VARCHAR(50))
INSERT INTO #t VALUES
(1, 'ABCDEF'),
(2, 'EFGHIJKLMNOPQ')
;WITH cte AS
(SELECT id, n, SUBSTRING(n, 1, 1) c, 1 AS ind FROM #t
UNION ALL
SELECT id, n, SUBSTRING(n, ind + 1, 1), ind + 1 FROM cte WHERE LEN(n) > ind
)
SELECT *
FROM cte
PIVOT (MAX(c) FOR ind IN([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[12],[13],[14],[15])) p
Output:
id n 1 2 3 4 5 6 7 8 9 10 12 13 14 15
1 ABCDEF A B C D E F NULL NULL NULL NULL NULL NULL NULL NULL
2 EFGHIJKLMNOPQ E F G H I J K L M N P Q NULL NULL
Here is dynamic version:
DECLARE #l INT, #c VARCHAR(MAX) = ''
SELECT #l = MAX(LEN(n)) FROM PivotTable
WHILE #l > 0
BEGIN
SET #c = ',[' + CAST(#l AS VARCHAR(MAX)) + ']' + #c
SET #l = #l - 1
END
SET #c = STUFF(#c, 1, 1,'')
DECLARE #s NVARCHAR(MAX) = '
;WITH cte AS
(SELECT id, n, SUBSTRING(n, 1, 1) c, 1 AS ind FROM PivotTable
UNION ALL
SELECT id, n, SUBSTRING(n, ind + 1, 1), ind + 1 FROM cte WHERE LEN(n) > ind
)
SELECT *
FROM cte
PIVOT (MAX(c) FOR ind IN(' + #c + ')) p'
EXEC (#s)
I am interpreting the question as putting the characters into one column ("split a string in a column into one character each into it's own column"). However, I realize that this might be ambiguous.
One method is with a recursive CTE:
with chars as (
select left(val, 1) as c, substring(val, 2, len(val)) as rest
from (select 'ABCDE' as val union all select '123') t
union all
select left(rest, 1), substring(rest, 2, len(rest))
from chars
where rest <> ''
)
select c
from chars;
Just plug in your table and column in the subquery. Note that you might want to include other columns as well.
Here is a SQL Fiddle.
If you want multiple columns and the number is not fixed, then you will need
dynamic SQL.
If you want a new column for every character you simply need:
SELECT [1] = SUBSTRING(Col, 1, 1),
[2] = SUBSTRING(Col, 2, 1),
[3] = SUBSTRING(Col, 3, 1),
[4] = SUBSTRING(Col, 4, 1),
[5] = SUBSTRING(Col, 5, 1),
[6] = SUBSTRING(Col, 6, 1),
[7] = SUBSTRING(Col, 7, 1),
[8] = SUBSTRING(Col, 8, 1),
[9] = SUBSTRING(Col, 9, 1)
FROM (VALUES ('ABCDE'), ('FGHIJKLMN')) t (Col);
Which is fine, if you have a know number of columns. If you have an unknown number of columns, then you just need to generate the same SQL with n columns. To do this you will need a numbers table, and since many people do not have one, I will do a quick demo on how to dynamically generate one.
The below will generate a sequential list of numbers, 1 - 100,000,000.
WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Numbers (Number) AS (SELECT ROW_NUMBER() OVER(ORDER BY N1.N) FROM N3 AS N1 CROSS JOIN N3 AS N2)
SELECT Number
FROM Numbers;
It simply uses a table valued constructor to generate 10 rows (N1), then cross joins these 10 rows to get 100 rows (N2), then cross joins these 100 rows to get 10,000 rows (N3) and so on and so on. It finally uses ROW_NUMBER() to get the sequential numbers.
This probably needs to be cut down for this use, I hope you are not splitting a string that is 100,000,000 characters long, but the principle applies. You can just use TOP and the maximum length of your string to limit it. For each number you can just build up the necessary repetetive SQL required, which is:
,[n] = SUBSTRING(Col, n, 1)
So you have something like:
SELECT Number,
[SQL] = ',[' + CAST(Number AS VARCHAR(10)) + '] = SUBSTRING(Col, ' + CAST(Number AS VARCHAR(10)) + ', 1)'
FROM Numbers;
Which gives something like:
Number SQL
-----------------------------------
1 ,[1] = SUBSTRING(Col, 1, 1)
2 ,[2] = SUBSTRING(Col, 2, 1)
3 ,[3] = SUBSTRING(Col, 3, 1)
4 ,[4] = SUBSTRING(Col, 4, 1)
The final step is to build up your final statement by concatenating all the text in the column SQL; the best way to do this is using SQL Server's XML Extensions.
So your final query might end up like:
DECLARE #SQL NVARCHAR(MAX) = '';
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
CREATE TABLE #T (Col VARCHAR(100));
INSERT #T (Col) VALUES ('ABCDE'), ('FGHIJKLMN');
WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Numbers (Number) AS (SELECT ROW_NUMBER() OVER(ORDER BY N1.N) FROM N3 AS N1 CROSS JOIN N3 AS N2)
SELECT #SQL = 'SELECT Col' +
( SELECT TOP (SELECT MAX(LEN(Col)) FROM #T)
',[' + CAST(Number AS VARCHAR(10)) + '] = SUBSTRING(Col, ' + CAST(Number AS VARCHAR(10)) + ', 1)'
FROM Numbers
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)') + '
FROM #T;';
EXECUTE sp_executesql #SQL;
Which gives:
Col 1 2 3 4 5 6 7 8 9
-------------------------------------------------
ABCDE A B C D E
FGHIJKLMN F G H I J K L M N
Finally, if you actually wanted to split it into rows, I would still use the same approach, with your adhoc numbers table, just join it to your original table:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL DROP TABLE #T;
CREATE TABLE #T (Col VARCHAR(100));
INSERT #T (Col) VALUES ('ABCDE'), ('FGHIJKLMN');
WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Numbers (Number) AS (SELECT TOP (SELECT MAX(LEN(Col)) FROM #T) ROW_NUMBER() OVER(ORDER BY N1.N) FROM N3 AS N1 CROSS JOIN N3 AS N2)
SELECT t.Col,
Position = n.Number,
Character = SUBSTRING(t.Col, n.Number, 1)
FROM #T AS t
INNER JOIN Numbers AS n
ON n.Number <= LEN(t.Col)
ORDER BY t.Col, n.Number;
Which gives something like:
Col Position Character
-------------------------------
ABCDE 1 A
ABCDE 2 B
ABCDE 3 C
ABCDE 4 D
ABCDE 5 E
One way
declare #str varchar(max) = 'ABCDE'
declare #sql nvarchar(max) = ''
declare #i int = 1
while (#i <= len(#str)) begin
set #sql += case when #i > 1 then ',' else '' end + '''' + substring(#str, #i, 1) + ''''
set #i += 1
end
exec('select ' + #sql)
(If ' can appear as a char you would need to substitute '')
This is a solution for a dynamic text length.
-- Generate demo data
CREATE TABLE #temp(col nvarchar(100))
INSERT INTO #temp(col)
VALUES(N'A'),(N'ABC'),(N'DEFGHI'),(N'AB'),(N'KLOMA')
-- Split all in multiple rows
CREATE TABLE #output (col nvarchar(100),part nchar(1), pos int)
;WITH cte AS(
SELECT col, LEFT(col, 1) as part, 1 as pos
FROM #temp
UNION ALL
SELECT col, SUBSTRING(col, pos+1,1) as part, pos+1 as part
FROM cte
WHERE LEN(col) > pos
)
INSERT INTO #output(col, part, pos)
SELECT col, part, pos
FROM cte
DECLARE #sql nvarchar(max), #columnlist nvarchar(max)
-- Generate Columlist for dynamic pivot
SELECT #columnlist = COALESCE(#columnlist + N',[' + CONVERT(nvarchar(max),pos) + ']', N'[' + CONVERT(nvarchar(max),pos) + ']')
FROM #output o
WHERE o.col = (SELECT TOP (1) col FROM #output ORDER BY LEN(col) DESC)
-- Pivoting for readability
SET #sql = N'
SELECT pvt.*
FROM #output o
PIVOT (
MAX(o.part)
FOR pos IN('+#columnlist+')
) as pvt'
EXEC (#sql)
-- Cleanup
DROP TABLE #temp
DROP TABLE #output
The keypart is the cte and the pivoting afterwards. If you have any questions, just give me a short feedback.